Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4fb9709

Browse files
committedJan 28, 2025
Implement a new kcfi_arity feature that encodes an indirect call target's arity (i.e., the number of live-in registers) in the function's __cfi header.
1 parent 5d6d982 commit 4fb9709

File tree

11 files changed

+268
-1
lines changed

11 files changed

+268
-1
lines changed
 

‎clang/docs/ControlFlowIntegrity.rst

+9
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,15 @@ cross-DSO function address equality. These properties make KCFI easier to
336336
adopt in low-level software. KCFI is limited to checking only function
337337
pointers, and isn't compatible with executable-only memory.
338338

339+
``-fsanitize-kcfi-arity``
340+
-----------------------------
341+
342+
For supported targets, this feature extends kCFI by telling the compiler to
343+
record information about each indirect-callable function's arity (i.e., the
344+
number of arguments passed in registers) into the binary. Some kernel CFI
345+
techniques, such as FineIBT, may be able to use this information to provide
346+
enhanced security.
347+
339348
Member Function Pointer Call Checking
340349
=====================================
341350

‎clang/docs/UsersManual.rst

+6
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,12 @@ are listed below.
22202220

22212221
This option is currently experimental.
22222222

2223+
.. option:: -fsanitize-kcfi-arity
2224+
2225+
Extends kernel indirect call forward-edge control flow integrity with
2226+
additional function arity information (for supported targets). See
2227+
:doc:`ControlFlowIntegrity` for more details.
2228+
22232229
.. option:: -fstrict-vtable-pointers
22242230

22252231
Enable optimizations based on the strict rules for overwriting polymorphic

‎clang/include/clang/Basic/CodeGenOptions.def

+1
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ CODEGENOPT(SanitizeCfiICallNormalizeIntegers, 1, 0) ///< Normalize integer types
277277
///< CFI icall function signatures
278278
CODEGENOPT(SanitizeCfiCanonicalJumpTables, 1, 0) ///< Make jump table symbols canonical
279279
///< instead of creating a local jump table.
280+
CODEGENOPT(SanitizeKcfiArity, 1, 0) ///< Embed arity in KCFI patchable function prefix
280281
CODEGENOPT(SanitizeCoverageType, 2, 0) ///< Type of sanitizer coverage
281282
///< instrumentation.
282283
CODEGENOPT(SanitizeCoverageIndirectCalls, 1, 0) ///< Enable sanitizer coverage

‎clang/include/clang/Basic/Features.def

+1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ FEATURE(is_trivially_constructible, LangOpts.CPlusPlus)
254254
FEATURE(is_trivially_copyable, LangOpts.CPlusPlus)
255255
FEATURE(is_union, LangOpts.CPlusPlus)
256256
FEATURE(kcfi, LangOpts.Sanitize.has(SanitizerKind::KCFI))
257+
FEATURE(kcfi_arity, LangOpts.Sanitize.has(SanitizerKind::KCFI))
257258
FEATURE(modules, LangOpts.Modules)
258259
FEATURE(safe_stack, LangOpts.Sanitize.has(SanitizerKind::SafeStack))
259260
FEATURE(shadow_call_stack,

‎clang/include/clang/Driver/Options.td

+4
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,10 @@ defm sanitize_cfi_canonical_jump_tables : BoolOption<"f", "sanitize-cfi-canonica
26192619
"Do not make">,
26202620
BothFlags<[], [ClangOption], " the jump table addresses canonical in the symbol table">>,
26212621
Group<f_clang_Group>;
2622+
def fsanitize_kcfi_arity : Flag<["-"], "fsanitize-kcfi-arity">,
2623+
Group<f_clang_Group>,
2624+
HelpText<"Embed function arity information into the KCFI patchable function prefix">,
2625+
MarshallingInfoFlag<CodeGenOpts<"SanitizeKcfiArity">>;
26222626
defm sanitize_stats : BoolOption<"f", "sanitize-stats",
26232627
CodeGenOpts<"SanitizeStats">, DefaultFalse,
26242628
PosFlag<SetTrue, [], [ClangOption], "Enable">,

‎clang/include/clang/Driver/SanitizerArgs.h

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class SanitizerArgs {
4343
bool CfiICallGeneralizePointers = false;
4444
bool CfiICallNormalizeIntegers = false;
4545
bool CfiCanonicalJumpTables = false;
46+
bool KcfiArity = false;
4647
int AsanFieldPadding = 0;
4748
bool SharedRuntime = false;
4849
bool StableABI = false;

‎clang/lib/CodeGen/CodeGenModule.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,8 @@ void CodeGenModule::Release() {
11491149
if (CodeGenOpts.PatchableFunctionEntryOffset)
11501150
getModule().addModuleFlag(llvm::Module::Override, "kcfi-offset",
11511151
CodeGenOpts.PatchableFunctionEntryOffset);
1152+
if (CodeGenOpts.SanitizeKcfiArity)
1153+
getModule().addModuleFlag(llvm::Module::Override, "kcfi-arity", 1);
11521154
}
11531155

11541156
if (CodeGenOpts.CFProtectionReturn &&

‎clang/lib/Driver/SanitizerArgs.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,8 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
829829
CfiICallNormalizeIntegers =
830830
Args.hasArg(options::OPT_fsanitize_cfi_icall_normalize_integers);
831831

832+
KcfiArity = Args.hasArg(options::OPT_fsanitize_kcfi_arity);
833+
832834
if (AllAddedKinds & SanitizerKind::CFI && DiagnoseErrors)
833835
D.Diag(diag::err_drv_argument_not_allowed_with)
834836
<< "-fsanitize=kcfi"
@@ -1383,6 +1385,14 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
13831385
if (CfiICallNormalizeIntegers)
13841386
CmdArgs.push_back("-fsanitize-cfi-icall-experimental-normalize-integers");
13851387

1388+
if (KcfiArity) {
1389+
if (!TC.getTriple().isOSLinux() || !TC.getTriple().isArch64Bit()) {
1390+
TC.getDriver().Diag(clang::diag::err_drv_kcfi_arity_unsupported_target)
1391+
<< TC.getTriple().str();
1392+
}
1393+
CmdArgs.push_back("-fsanitize-kcfi-arity");
1394+
}
1395+
13861396
if (CfiCanonicalJumpTables)
13871397
CmdArgs.push_back("-fsanitize-cfi-canonical-jump-tables");
13881398

‎clang/test/CodeGen/kcfi-arity.c

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -fsanitize-kcfi-arity -o - %s | FileCheck %s
2+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fsanitize=kcfi -fsanitize-kcfi-arity -x c++ -o - %s | FileCheck %s
3+
#if !__has_feature(kcfi_arity)
4+
#error Missing kcfi_arity?
5+
#endif
6+
7+
// CHECK: ![[#]] = !{i32 4, !"kcfi-arity", i32 1}

‎llvm/lib/Target/X86/X86AsmPrinter.cpp

+22-1
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,29 @@ void X86AsmPrinter::emitKCFITypeId(const MachineFunction &MF) {
181181
// Embed the type hash in the X86::MOV32ri instruction to avoid special
182182
// casing object file parsers.
183183
EmitKCFITypePadding(MF);
184+
unsigned DestReg = X86::EAX;
185+
186+
if (F.getParent()->getModuleFlag("kcfi-arity")) {
187+
// The ArityToRegMap assumes the 64-bit Linux kernel ABI
188+
const auto &Triple = MF.getTarget().getTargetTriple();
189+
assert(Triple.isArch64Bit() && Triple.isOSLinux());
190+
191+
// Determine the function's arity (i.e., the number of arguments) at the ABI
192+
// level by counting the number of parameters that are passed
193+
// as registers, such as pointers and 64-bit (or smaller) integers. The
194+
// Linux x86-64 ABI allows up to 6 parameters to be passed in GPRs.
195+
// Additional parameters or parameters larger than 64 bits may be passed on
196+
// the stack, in which case the arity is denoted as 7.
197+
const unsigned ArityToRegMap[8] = {X86::EAX, X86::ECX, X86::EDX, X86::EBX,
198+
X86::ESP, X86::EBP, X86::ESI, X86::EDI};
199+
int Arity = MF.getInfo<X86MachineFunctionInfo>()->getArgumentStackSize() > 0
200+
? 7
201+
: MF.getRegInfo().liveins().size();
202+
DestReg = ArityToRegMap[Arity];
203+
}
204+
184205
EmitAndCountInstruction(MCInstBuilder(X86::MOV32ri)
185-
.addReg(X86::EAX)
206+
.addReg(DestReg)
186207
.addImm(MaskKCFIType(Type->getZExtValue())));
187208

188209
if (MAI->hasDotTypeDotSizeDirective()) {

‎llvm/test/CodeGen/X86/kcfi-arity.ll

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -verify-machineinstrs < %s | FileCheck %s --check-prefix=ASM
2+
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -verify-machineinstrs -stop-after=finalize-isel < %s | FileCheck %s --check-prefixes=MIR,ISEL
3+
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -verify-machineinstrs -stop-after=kcfi < %s | FileCheck %s --check-prefixes=MIR,KCFI
4+
5+
; ASM: .p2align 4
6+
; ASM: .type __cfi_f1,@function
7+
; ASM-LABEL: __cfi_f1:
8+
; ASM-NEXT: nop
9+
; ASM-NEXT: nop
10+
; ASM-NEXT: nop
11+
; ASM-NEXT: nop
12+
; ASM-NEXT: nop
13+
; ASM-NEXT: nop
14+
; ASM-NEXT: nop
15+
; ASM-NEXT: nop
16+
; ASM-NEXT: nop
17+
; ASM-NEXT: nop
18+
; ASM-NEXT: nop
19+
; ASM-NEXT: movl $12345678, %ecx
20+
; ASM-LABEL: .Lcfi_func_end0:
21+
; ASM-NEXT: .size __cfi_f1, .Lcfi_func_end0-__cfi_f1
22+
define void @f1(ptr noundef %x) !kcfi_type !1 {
23+
; ASM-LABEL: f1:
24+
; ASM: # %bb.0:
25+
; ASM: movl $4282621618, %r10d # imm = 0xFF439EB2
26+
; ASM-NEXT: addl -4(%rdi), %r10d
27+
; ASM-NEXT: je .Ltmp0
28+
; ASM-NEXT: .Ltmp1:
29+
; ASM-NEXT: ud2
30+
; ASM-NEXT: .section .kcfi_traps,"ao",@progbits,.text
31+
; ASM-NEXT: .Ltmp2:
32+
; ASM-NEXT: .long .Ltmp1-.Ltmp2
33+
; ASM-NEXT: .text
34+
; ASM-NEXT: .Ltmp0:
35+
; ASM-NEXT: callq *%rdi
36+
37+
; MIR-LABEL: name: f1
38+
; MIR: body:
39+
; ISEL: CALL64r %0, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, cfi-type 12345678
40+
; KCFI: BUNDLE{{.*}} {
41+
; KCFI-NEXT: KCFI_CHECK $rdi, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
42+
; KCFI-NEXT: CALL64r killed $rdi, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp
43+
; KCFI-NEXT: }
44+
call void %x() [ "kcfi"(i32 12345678) ]
45+
ret void
46+
}
47+
48+
; ASM-NOT: __cfi_f2:
49+
define void @f2(ptr noundef %x) {
50+
; ASM-LABEL: f2:
51+
52+
; MIR-LABEL: name: f2
53+
; MIR: body:
54+
; ISEL: TCRETURNri64 %0, 0, csr_64, implicit $rsp, implicit $ssp, cfi-type 12345678
55+
; KCFI: BUNDLE{{.*}} {
56+
; KCFI-NEXT: KCFI_CHECK $rdi, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
57+
; KCFI-NEXT: TAILJMPr64 killed $rdi, csr_64, implicit $rsp, implicit $ssp, implicit $rsp, implicit $ssp
58+
; KCFI-NEXT: }
59+
tail call void %x() [ "kcfi"(i32 12345678) ]
60+
ret void
61+
}
62+
63+
; ASM-NOT: __cfi_f3:
64+
define void @f3(ptr noundef %x) #0 {
65+
; ASM-LABEL: f3:
66+
; MIR-LABEL: name: f3
67+
; MIR: body:
68+
; ISEL: CALL64pcrel32 &__llvm_retpoline_r11, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit killed $r11, cfi-type 12345678
69+
; KCFI: BUNDLE{{.*}} {
70+
; KCFI-NEXT: KCFI_CHECK $r11, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
71+
; KCFI-NEXT: CALL64pcrel32 &__llvm_retpoline_r11, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit internal killed $r11
72+
; KCFI-NEXT: }
73+
call void %x() [ "kcfi"(i32 12345678) ]
74+
ret void
75+
}
76+
77+
; ASM-NOT: __cfi_f4:
78+
define void @f4(ptr noundef %x) #0 {
79+
; ASM-LABEL: f4:
80+
; MIR-LABEL: name: f4
81+
; MIR: body:
82+
; ISEL: TCRETURNdi64 &__llvm_retpoline_r11, 0, csr_64, implicit $rsp, implicit $ssp, implicit killed $r11, cfi-type 12345678
83+
; KCFI: BUNDLE{{.*}} {
84+
; KCFI-NEXT: KCFI_CHECK $r11, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
85+
; KCFI-NEXT: TAILJMPd64 &__llvm_retpoline_r11, csr_64, implicit $rsp, implicit $ssp, implicit $rsp, implicit $ssp, implicit internal killed $r11
86+
; KCFI-NEXT: }
87+
tail call void %x() [ "kcfi"(i32 12345678) ]
88+
ret void
89+
}
90+
91+
;; Ensure we emit Value + 1 for unwanted values (e.g. endbr64 == 4196274163).
92+
; ASM-LABEL: __cfi_f5:
93+
; ASM: movl $4196274164, %ecx # imm = 0xFA1E0FF4
94+
define void @f5(ptr noundef %x) !kcfi_type !2 {
95+
; ASM-LABEL: f5:
96+
; ASM: movl $98693132, %r10d # imm = 0x5E1F00C
97+
tail call void %x() [ "kcfi"(i32 4196274163) ]
98+
ret void
99+
}
100+
101+
;; Ensure we emit Value + 1 for unwanted values (e.g. -endbr64 == 98693133).
102+
; ASM-LABEL: __cfi_f6:
103+
; ASM: movl $98693134, %ecx # imm = 0x5E1F00E
104+
define void @f6(ptr noundef %x) !kcfi_type !3 {
105+
; ASM-LABEL: f6:
106+
; ASM: movl $4196274162, %r10d # imm = 0xFA1E0FF2
107+
tail call void %x() [ "kcfi"(i32 98693133) ]
108+
ret void
109+
}
110+
111+
@g = external local_unnamed_addr global ptr, align 8
112+
113+
define void @f7() {
114+
; MIR-LABEL: name: f7
115+
; MIR: body:
116+
; ISEL: TCRETURNmi64 killed %0, 1, $noreg, 0, $noreg, 0, csr_64, implicit $rsp, implicit $ssp, cfi-type 12345678
117+
; KCFI: $r11 = MOV64rm killed renamable $rax, 1, $noreg, 0, $noreg
118+
; KCFI-NEXT: BUNDLE{{.*}} {
119+
; KCFI-NEXT: KCFI_CHECK $r11, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
120+
; KCFI-NEXT: TAILJMPr64 internal $r11, csr_64, implicit $rsp, implicit $ssp, implicit $rsp, implicit $ssp
121+
; KCFI-NEXT: }
122+
%1 = load ptr, ptr @g, align 8
123+
tail call void %1() [ "kcfi"(i32 12345678) ]
124+
ret void
125+
}
126+
127+
define void @f8() {
128+
; MIR-LABEL: name: f8
129+
; MIR: body:
130+
; ISEL: CALL64m killed %0, 1, $noreg, 0, $noreg, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, cfi-type 12345678
131+
; KCFI: $r11 = MOV64rm killed renamable $rax, 1, $noreg, 0, $noreg
132+
; KCFI-NEXT: BUNDLE{{.*}} {
133+
; KCFI-NEXT: KCFI_CHECK $r11, 12345678, implicit-def $r10, implicit-def $r11, implicit-def $eflags
134+
; KCFI-NEXT: CALL64r internal $r11, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp
135+
; KCFI-NEXT: }
136+
%1 = load ptr, ptr @g, align 8
137+
call void %1() [ "kcfi"(i32 12345678) ]
138+
ret void
139+
}
140+
141+
%struct.S9 = type { [10 x i64] }
142+
143+
;; Ensure that functions with large (e.g., greater than 8 bytes) arguments passed on the stack are assigned arity=7
144+
; ASM-LABEL: __cfi_f9:
145+
; ASM: movl $199571451, %edi # imm = 0xBE537FB
146+
define dso_local void @f9(ptr noundef byval(%struct.S9) align 8 %s) !kcfi_type !4 {
147+
entry:
148+
ret void
149+
}
150+
151+
;; Ensure that functions with fewer than 7 register arguments and no stack arguments are assigned arity<7
152+
; ASM-LABEL: __cfi_f10:
153+
; ASM: movl $1046421190, %esi # imm = 0x3E5F1EC6
154+
define dso_local void @f10(i32 noundef %v1, i32 noundef %v2, i32 noundef %v3, i32 noundef %v4, i32 noundef %v5, i32 noundef %v6) #0 !kcfi_type !5 {
155+
entry:
156+
%v1.addr = alloca i32, align 4
157+
%v2.addr = alloca i32, align 4
158+
%v3.addr = alloca i32, align 4
159+
%v4.addr = alloca i32, align 4
160+
%v5.addr = alloca i32, align 4
161+
%v6.addr = alloca i32, align 4
162+
store i32 %v1, ptr %v1.addr, align 4
163+
store i32 %v2, ptr %v2.addr, align 4
164+
store i32 %v3, ptr %v3.addr, align 4
165+
store i32 %v4, ptr %v4.addr, align 4
166+
store i32 %v5, ptr %v5.addr, align 4
167+
store i32 %v6, ptr %v6.addr, align 4
168+
ret void
169+
}
170+
171+
;; Ensure that functions with greater than 7 register arguments and no stack arguments are assigned arity=7
172+
; ASM-LABEL: __cfi_f11:
173+
; ASM: movl $1342488295, %edi # imm = 0x5004BEE7
174+
define dso_local void @f11(i32 noundef %v1, i32 noundef %v2, i32 noundef %v3, i32 noundef %v4, i32 noundef %v5, i32 noundef %v6, i32 noundef %v7, i32 noundef %v8) #0 !kcfi_type !6 {
175+
entry:
176+
%v1.addr = alloca i32, align 4
177+
%v2.addr = alloca i32, align 4
178+
%v3.addr = alloca i32, align 4
179+
%v4.addr = alloca i32, align 4
180+
%v5.addr = alloca i32, align 4
181+
%v6.addr = alloca i32, align 4
182+
%v7.addr = alloca i32, align 4
183+
%v8.addr = alloca i32, align 4
184+
store i32 %v1, ptr %v1.addr, align 4
185+
store i32 %v2, ptr %v2.addr, align 4
186+
store i32 %v3, ptr %v3.addr, align 4
187+
store i32 %v4, ptr %v4.addr, align 4
188+
store i32 %v5, ptr %v5.addr, align 4
189+
store i32 %v6, ptr %v6.addr, align 4
190+
store i32 %v7, ptr %v7.addr, align 4
191+
store i32 %v8, ptr %v8.addr, align 4
192+
ret void
193+
}
194+
195+
attributes #0 = { "target-features"="+retpoline-indirect-branches,+retpoline-indirect-calls" }
196+
197+
!llvm.module.flags = !{!0, !7}
198+
!0 = !{i32 4, !"kcfi", i32 1}
199+
!1 = !{i32 12345678}
200+
!2 = !{i32 4196274163}
201+
!3 = !{i32 98693133}
202+
!4 = !{i32 199571451}
203+
!5 = !{i32 1046421190}
204+
!6 = !{i32 1342488295}
205+
!7 = !{i32 4, !"kcfi-arity", i32 1}

0 commit comments

Comments
 (0)
Failed to load comments.