Skip to content

Commit 71c1293

Browse files
committed
[ARM][KCFI] Add backend support for Kernel Control-Flow Integrity
Implement KCFI (Kernel Control Flow Integrity) backend support for ARM32, Thumb2, and Thumb1. The Linux kernel has supported ARM KCFI via Clang's generic KCFI implementation, but this has finally started to [cause problems](ClangBuiltLinux/linux#2124) so it's time to get the KCFI operand bundle lowering working on ARM. Supports patchable-function-prefix with adjusted load offsets. ARM implementation notes: - Four-instruction EOR sequence builds the 32-bit type ID byte-by-byte to work within ARM's modified immediate encoding constraints. - Scratch register selection: r12 (IP) is preferred, r3 used as fallback when r12 holds the call target. r3 gets spilled/reloaded if it is being used as a call argument. - UDF trap encoding: 0x8000 | (0x1F << 5) | target_reg_index, similar to aarch64's trap encoding. Thumb2 implementation notes: - Logically the same as ARM - UDF trap encoding: 0x80 | target_reg_index Thumb1 implementation notes: - Due to register pressure, 2 scratch registers are needed: r3 and r2, which get spilled/reloaded if they are being used as call args. - Instead of EOR, add/lsl sequence to load immediate, followed by a compare. - No trap encoding. Update tests to validate all three sub targets.
1 parent 20fdd53 commit 71c1293

File tree

14 files changed

+1069
-29
lines changed

14 files changed

+1069
-29
lines changed

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,8 @@ static void addKCFIPass(const Triple &TargetTriple, const LangOptions &LangOpts,
687687
PassBuilder &PB) {
688688
// If the back-end supports KCFI operand bundle lowering, skip KCFIPass.
689689
if (TargetTriple.getArch() == llvm::Triple::x86_64 ||
690-
TargetTriple.isAArch64(64) || TargetTriple.isRISCV())
690+
TargetTriple.isAArch64(64) || TargetTriple.isRISCV() ||
691+
TargetTriple.isARM() || TargetTriple.isThumb())
691692
return;
692693

693694
// Ensure we lower KCFI operand bundles with -O0.

llvm/lib/Target/ARM/ARMAsmPrinter.cpp

Lines changed: 457 additions & 0 deletions
Large diffs are not rendered by default.

llvm/lib/Target/ARM/ARMAsmPrinter.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,20 @@ class LLVM_LIBRARY_VISIBILITY ARMAsmPrinter : public AsmPrinter {
123123
void LowerPATCHABLE_FUNCTION_EXIT(const MachineInstr &MI);
124124
void LowerPATCHABLE_TAIL_CALL(const MachineInstr &MI);
125125

126+
// KCFI check lowering
127+
void LowerKCFI_CHECK(const MachineInstr &MI);
128+
126129
private:
127130
void EmitSled(const MachineInstr &MI, SledKind Kind);
128131

132+
// KCFI check emission helpers
133+
void EmitKCFI_CHECK_ARM32(Register AddrReg, int64_t Type,
134+
const MachineInstr &Call, int64_t PrefixNops);
135+
void EmitKCFI_CHECK_Thumb2(Register AddrReg, int64_t Type,
136+
const MachineInstr &Call, int64_t PrefixNops);
137+
void EmitKCFI_CHECK_Thumb1(Register AddrReg, int64_t Type,
138+
const MachineInstr &Call, int64_t PrefixNops);
139+
129140
// Helpers for emitStartOfAsmFile() and emitEndOfAsmFile()
130141
void emitAttributes();
131142

llvm/lib/Target/ARM/ARMExpandPseudoInsts.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,8 @@ bool ARMExpandPseudo::ExpandMI(MachineBasicBlock &MBB,
23012301
for (unsigned i = 2, e = MBBI->getNumOperands(); i != e; ++i)
23022302
NewMI->addOperand(MBBI->getOperand(i));
23032303

2304+
NewMI->setCFIType(*MBB.getParent(), MI.getCFIType());
2305+
23042306
// Update call info and delete the pseudo instruction TCRETURN.
23052307
if (MI.isCandidateForAdditionalCallInfo())
23062308
MI.getMF()->moveAdditionalCallInfo(&MI, &*NewMI);

llvm/lib/Target/ARM/ARMISelLowering.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,13 +2848,17 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
28482848
if (isTailCall) {
28492849
MF.getFrameInfo().setHasTailCall();
28502850
SDValue Ret = DAG.getNode(ARMISD::TC_RETURN, dl, MVT::Other, Ops);
2851+
if (CLI.CFIType)
2852+
Ret.getNode()->setCFIType(CLI.CFIType->getZExtValue());
28512853
DAG.addNoMergeSiteInfo(Ret.getNode(), CLI.NoMerge);
28522854
DAG.addCallSiteInfo(Ret.getNode(), std::move(CSInfo));
28532855
return Ret;
28542856
}
28552857

28562858
// Returns a chain and a flag for retval copy to use.
28572859
Chain = DAG.getNode(CallOpc, dl, {MVT::Other, MVT::Glue}, Ops);
2860+
if (CLI.CFIType)
2861+
Chain.getNode()->setCFIType(CLI.CFIType->getZExtValue());
28582862
DAG.addNoMergeSiteInfo(Chain.getNode(), CLI.NoMerge);
28592863
InGlue = Chain.getValue(1);
28602864
DAG.addCallSiteInfo(Chain.getNode(), std::move(CSInfo));
@@ -12007,6 +12011,59 @@ static void genTPLoopBody(MachineBasicBlock *TpLoopBody,
1200712011
.add(predOps(ARMCC::AL));
1200812012
}
1200912013

12014+
bool ARMTargetLowering::supportKCFIBundles() const {
12015+
// KCFI is supported in all ARM/Thumb modes
12016+
return true;
12017+
}
12018+
12019+
MachineInstr *
12020+
ARMTargetLowering::EmitKCFICheck(MachineBasicBlock &MBB,
12021+
MachineBasicBlock::instr_iterator &MBBI,
12022+
const TargetInstrInfo *TII) const {
12023+
assert(MBBI->isCall() && MBBI->getCFIType() &&
12024+
"Invalid call instruction for a KCFI check");
12025+
12026+
MachineOperand *TargetOp = nullptr;
12027+
switch (MBBI->getOpcode()) {
12028+
// ARM mode opcodes
12029+
case ARM::BLX:
12030+
case ARM::BLX_pred:
12031+
case ARM::BLX_noip:
12032+
case ARM::BLX_pred_noip:
12033+
case ARM::BX_CALL:
12034+
TargetOp = &MBBI->getOperand(0);
12035+
break;
12036+
case ARM::TCRETURNri:
12037+
case ARM::TCRETURNrinotr12:
12038+
case ARM::TAILJMPr:
12039+
case ARM::TAILJMPr4:
12040+
TargetOp = &MBBI->getOperand(0);
12041+
break;
12042+
// Thumb mode opcodes (Thumb1 and Thumb2)
12043+
// Note: Most Thumb call instructions have predicate operands before the
12044+
// target register Format: tBLXr pred, predreg, target_register, ...
12045+
case ARM::tBLXr: // Thumb1/Thumb2: BLX register (requires V5T)
12046+
case ARM::tBLXr_noip: // Thumb1/Thumb2: BLX register, no IP clobber
12047+
case ARM::tBX_CALL: // Thumb1 only: BX call (push LR, BX)
12048+
TargetOp = &MBBI->getOperand(2);
12049+
break;
12050+
// Tail call instructions don't have predicates, target is operand 0
12051+
case ARM::tTAILJMPr: // Thumb1/Thumb2: Tail call via register
12052+
TargetOp = &MBBI->getOperand(0);
12053+
break;
12054+
default:
12055+
llvm_unreachable("Unexpected CFI call opcode");
12056+
}
12057+
12058+
assert(TargetOp && TargetOp->isReg() && "Invalid target operand");
12059+
TargetOp->setIsRenamable(false);
12060+
12061+
return BuildMI(MBB, MBBI, MBBI->getDebugLoc(), TII->get(ARM::KCFI_CHECK))
12062+
.addReg(TargetOp->getReg())
12063+
.addImm(MBBI->getCFIType())
12064+
.getInstr();
12065+
}
12066+
1201012067
MachineBasicBlock *
1201112068
ARMTargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
1201212069
MachineBasicBlock *BB) const {

llvm/lib/Target/ARM/ARMISelLowering.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,12 @@ class VectorType;
447447
void AdjustInstrPostInstrSelection(MachineInstr &MI,
448448
SDNode *Node) const override;
449449

450+
bool supportKCFIBundles() const override;
451+
452+
MachineInstr *EmitKCFICheck(MachineBasicBlock &MBB,
453+
MachineBasicBlock::instr_iterator &MBBI,
454+
const TargetInstrInfo *TII) const override;
455+
450456
SDValue PerformCMOVCombine(SDNode *N, SelectionDAG &DAG) const;
451457
SDValue PerformBRCONDCombine(SDNode *N, SelectionDAG &DAG) const;
452458
SDValue PerformCMOVToBFICombine(SDNode *N, SelectionDAG &DAG) const;

llvm/lib/Target/ARM/ARMInstrInfo.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6535,6 +6535,15 @@ def CMP_SWAP_64 : PseudoInst<(outs GPRPair:$Rd, GPRPair:$addr_temp_out),
65356535

65366536
def : Pat<(atomic_fence (timm), 0), (MEMBARRIER)>;
65376537

6538+
//===----------------------------------------------------------------------===//
6539+
// KCFI check pseudo-instruction.
6540+
//===----------------------------------------------------------------------===//
6541+
let isPseudo = 1 in {
6542+
def KCFI_CHECK
6543+
: PseudoInst<(outs), (ins GPR:$ptr, i32imm:$type), NoItinerary, []>,
6544+
Sched<[]>;
6545+
}
6546+
65386547
//===----------------------------------------------------------------------===//
65396548
// Instructions used for emitting unwind opcodes on Windows.
65406549
//===----------------------------------------------------------------------===//

llvm/lib/Target/ARM/ARMTargetMachine.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeARMTarget() {
111111
initializeMVELaneInterleavingPass(Registry);
112112
initializeARMFixCortexA57AES1742098Pass(Registry);
113113
initializeARMDAGToDAGISelLegacyPass(Registry);
114+
initializeKCFIPass(Registry);
114115
}
115116

116117
static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) {
@@ -487,6 +488,9 @@ void ARMPassConfig::addPreSched2() {
487488
// proper scheduling.
488489
addPass(createARMExpandPseudoPass());
489490

491+
// Emit KCFI checks for indirect calls.
492+
addPass(createKCFIPass());
493+
490494
if (getOptLevel() != CodeGenOptLevel::None) {
491495
// When optimising for size, always run the Thumb2SizeReduction pass before
492496
// IfConversion. Otherwise, check whether IT blocks are restricted
@@ -530,6 +534,9 @@ void ARMPassConfig::addPreEmitPass() {
530534
}
531535

532536
void ARMPassConfig::addPreEmitPass2() {
537+
// Unpack KCFI bundles before AsmPrinter
538+
addPass(createUnpackMachineBundles(nullptr));
539+
533540
// Inserts fixup instructions before unsafe AES operations. Instructions may
534541
// be inserted at the start of blocks and at within blocks so this pass has to
535542
// come before those below.

llvm/test/CodeGen/ARM/O3-pipeline.ll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
; CHECK-NEXT: ARM Execution Domain Fix
167167
; CHECK-NEXT: BreakFalseDeps
168168
; CHECK-NEXT: ARM pseudo instruction expansion pass
169+
; CHECK-NEXT: Insert KCFI indirect call checks
169170
; CHECK-NEXT: Thumb2 instruction size reduce pass
170171
; CHECK-NEXT: MachineDominator Tree Construction
171172
; CHECK-NEXT: Machine Natural Loop Construction
@@ -204,6 +205,7 @@
204205
; CHECK-NEXT: Lazy Machine Block Frequency Analysis
205206
; CHECK-NEXT: Machine Optimization Remark Emitter
206207
; CHECK-NEXT: Stack Frame Layout Analysis
208+
; CHECK-NEXT: Unpack machine instruction bundles
207209
; CHECK-NEXT: Reaching Definitions Analysis
208210
; CHECK-NEXT: ARM fix for Cortex-A57 AES Erratum 1742098
209211
; CHECK-NEXT: ARM Branch Targets

llvm/test/CodeGen/ARM/kcfi-arm.ll

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs < %s | FileCheck %s --check-prefix=ASM
2+
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs -stop-after=finalize-isel < %s | FileCheck %s --check-prefixes=MIR,ISEL
3+
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs -stop-after=kcfi < %s | FileCheck %s --check-prefixes=MIR,KCFI
4+
5+
; ASM: .long 12345678
6+
define void @f1(ptr noundef %x) !kcfi_type !1 {
7+
; ASM-LABEL: f1:
8+
; ASM: @ %bb.0:
9+
; ASM: bic r12, r0, #1
10+
; ASM-NEXT: ldr r12, [r12, #-4]
11+
; ASM-NEXT: eor r12, r12, #78
12+
; ASM-NEXT: eor r12, r12, #24832
13+
; ASM-NEXT: eor r12, r12, #12320768
14+
; ASM-NEXT: eors r12, r12, #0
15+
; ASM-NEXT: beq .Ltmp{{[0-9]+}}
16+
; UDF encoding: 0x8000 | (0x1F << 5) | r0 = 0x83e0 = 33760
17+
; ASM-NEXT: udf #33760
18+
; ASM-NEXT: .Ltmp{{[0-9]+}}:
19+
; ASM-NEXT: blx r0
20+
21+
; MIR-LABEL: name: f1
22+
; MIR: body:
23+
24+
; ISEL: BLX %0, csr_aapcs,{{.*}} cfi-type 12345678
25+
26+
; KCFI: BUNDLE{{.*}} {
27+
; KCFI-NEXT: KCFI_CHECK $r0, 12345678
28+
; KCFI-NEXT: BLX killed $r0, csr_aapcs,{{.*}}
29+
; KCFI-NEXT: }
30+
31+
call void %x() [ "kcfi"(i32 12345678) ]
32+
ret void
33+
}
34+
35+
; Test with tail call
36+
define void @f2(ptr noundef %x) !kcfi_type !1 {
37+
; ASM-LABEL: f2:
38+
; ASM: @ %bb.0:
39+
; ASM: bic r12, r0, #1
40+
; ASM: ldr r12, [r12, #-4]
41+
; ASM: eor r12, r12, #78
42+
; ASM: eor r12, r12, #24832
43+
; ASM: eor r12, r12, #12320768
44+
; ASM: eors r12, r12, #0
45+
; ASM: beq .Ltmp{{[0-9]+}}
46+
; UDF encoding: 0x8000 | (0x1F << 5) | r0 = 0x83e0 = 33760
47+
; ASM: udf #33760
48+
; ASM: .Ltmp{{[0-9]+}}:
49+
; ASM: bx r0
50+
51+
; MIR-LABEL: name: f2
52+
; MIR: body:
53+
54+
; ISEL: TCRETURNri %0, 0, csr_aapcs, implicit $sp, cfi-type 12345678
55+
56+
; KCFI: BUNDLE{{.*}} {
57+
; KCFI-NEXT: KCFI_CHECK $r0, 12345678
58+
; KCFI-NEXT: TAILJMPr killed $r0, csr_aapcs, implicit $sp, implicit $sp
59+
; KCFI-NEXT: }
60+
61+
tail call void %x() [ "kcfi"(i32 12345678) ]
62+
ret void
63+
}
64+
65+
; Test r3 spill/reload when target is r12 and r3 is a call argument.
66+
; With 5+ arguments (target + 4 args), r0-r3 are all used for arguments,
67+
; forcing r3 to be spilled when we need it as scratch register.
68+
define void @f3_r3_spill(ptr noundef %target, i32 %a, i32 %b, i32 %c, i32 %d) !kcfi_type !1 {
69+
; ASM-LABEL: f3_r3_spill:
70+
; ASM: @ %bb.0:
71+
; Arguments: r0=%target, r1=%a, r2=%b, r3=%c, [sp]=%d
72+
; Call needs: r0=%a, r1=%b, r2=%c, r3=%d, target in r12
73+
; Compiler shuffles arguments into place, saving r3 (c) in lr, loading d from stack
74+
; ASM: mov lr, r3
75+
; ASM-NEXT: ldr r3, [sp, #8]
76+
; ASM-NEXT: mov r12, r0
77+
; ASM-NEXT: mov r0, r1
78+
; ASM-NEXT: mov r1, r2
79+
; ASM-NEXT: mov r2, lr
80+
; r3 is live as 4th argument, so push it before KCFI check
81+
; ASM-NEXT: stmdb sp!, {r3}
82+
; ASM-NEXT: bic r3, r12, #1
83+
; ASM-NEXT: ldr r3, [r3, #-4]
84+
; ASM-NEXT: eor r3, r3, #78
85+
; ASM-NEXT: eor r3, r3, #24832
86+
; ASM-NEXT: eor r3, r3, #12320768
87+
; ASM-NEXT: eors r3, r3, #0
88+
; Restore r3 immediately after comparison, before branch
89+
; ASM-NEXT: ldm sp!, {r3}
90+
; ASM-NEXT: beq .Ltmp{{[0-9]+}}
91+
; UDF encoding: 0x8000 | (0x1F << 5) | r12 = 0x83ec = 33772
92+
; ASM-NEXT: udf #33772
93+
; ASM-NEXT: .Ltmp{{[0-9]+}}:
94+
; ASM-NEXT: blx r12
95+
;
96+
call void %target(i32 %a, i32 %b, i32 %c, i32 %d) [ "kcfi"(i32 12345678) ]
97+
ret void
98+
}
99+
100+
; Test with 3 arguments - r3 not live, target in r12, so r3 used as scratch without spilling
101+
define void @f4_r3_unused(ptr noundef %target, i32 %a, i32 %b) !kcfi_type !1 {
102+
; ASM-LABEL: f4_r3_unused:
103+
; ASM: @ %bb.0:
104+
; Only 3 arguments total, so r3 is not used as call argument
105+
; Compiler puts target→r3, a→r0, b→r1
106+
; ASM: mov r3, r0
107+
; ASM-NEXT: mov r0, r1
108+
; ASM-NEXT: mov r1, r2
109+
; r3 is the target, so we use r12 as scratch (no spill needed)
110+
; ASM-NEXT: bic r12, r3, #1
111+
; ASM-NEXT: ldr r12, [r12, #-4]
112+
; ASM-NEXT: eor r12, r12, #78
113+
; ASM-NEXT: eor r12, r12, #24832
114+
; ASM-NEXT: eor r12, r12, #12320768
115+
; ASM-NEXT: eors r12, r12, #0
116+
; ASM-NEXT: beq .Ltmp{{[0-9]+}}
117+
; UDF encoding: 0x8000 | (0x1F << 5) | r3 = 0x83e3 = 33763
118+
; ASM-NEXT: udf #33763
119+
; ASM-NEXT: .Ltmp{{[0-9]+}}:
120+
; ASM-NEXT: blx r3
121+
;
122+
call void %target(i32 %a, i32 %b) [ "kcfi"(i32 12345678) ]
123+
ret void
124+
}
125+
126+
!llvm.module.flags = !{!0}
127+
!0 = !{i32 4, !"kcfi", i32 1}
128+
!1 = !{i32 12345678}

0 commit comments

Comments
 (0)