Skip to content

Commit fa9304a

Browse files
committed
[ARM][KCFI] Add backend support for Kernel Control-Flow Integrity
Implement KCFI (Kernel Control Flow Integrity) backend support for ARM32 (ARM mode only, not Thumb), as is already supported for x86, aarch64, and riscv. 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. 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 - Automatic r3 spill/reload when r3 is live as a call argument (5+ args) - UDF trap encoding: 0x8000 | (0x1F << 5) | target_reg_index, similar to aarch64's trap encoding. - Support for patchable-function-prefix with adjusted load offsets - Only enabled for ARM mode Frontend integration updated to skip the KCFI IR pass for ARM targets, allowing the backend to handle KCFI operand bundle lowering directly, matching the implementation used by the other architectures.
1 parent 20fdd53 commit fa9304a

File tree

12 files changed

+425
-1
lines changed

12 files changed

+425
-1
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())
691692
return;
692693

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

llvm/lib/Target/ARM/ARMAsmPrinter.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,121 @@ void ARMAsmPrinter::EmitUnwindingInstruction(const MachineInstr *MI) {
14711471
// instructions) auto-generated.
14721472
#include "ARMGenMCPseudoLowering.inc"
14731473

1474+
void ARMAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
1475+
Register AddrReg = MI.getOperand(0).getReg();
1476+
const int64_t Type = MI.getOperand(1).getImm();
1477+
1478+
// Get the call instruction that follows this KCFI_CHECK.
1479+
assert(std::next(MI.getIterator())->isCall() &&
1480+
"KCFI_CHECK not followed by a call instruction");
1481+
const MachineInstr &Call = *std::next(MI.getIterator());
1482+
1483+
// Choose scratch register (r12 or r3): r12 (IP) is the primary choice;
1484+
// use r3 if r12 is the target.
1485+
unsigned ScratchReg = ARM::R12;
1486+
bool NeedSpillR3 = false;
1487+
1488+
if (AddrReg == ARM::R12) {
1489+
ScratchReg = ARM::R3;
1490+
1491+
// Check if r3 is live (used as implicit operand in the call).
1492+
// If so, we need to spill/restore it.
1493+
for (const MachineOperand &MO : Call.implicit_operands()) {
1494+
if (MO.isReg() && MO.getReg() == ARM::R3 && MO.isUse()) {
1495+
NeedSpillR3 = true;
1496+
break;
1497+
}
1498+
}
1499+
}
1500+
1501+
// Adjust the offset for patchable-function-prefix.
1502+
int64_t PrefixNops = 0;
1503+
MI.getMF()
1504+
->getFunction()
1505+
.getFnAttribute("patchable-function-prefix")
1506+
.getValueAsString()
1507+
.getAsInteger(10, PrefixNops);
1508+
1509+
// If we need to spill r3, push it first.
1510+
if (NeedSpillR3) {
1511+
// push {r3}
1512+
EmitToStreamer(*OutStreamer,
1513+
MCInstBuilder(ARM::STMDB_UPD)
1514+
.addReg(ARM::SP)
1515+
.addReg(ARM::SP)
1516+
.addImm(ARMCC::AL)
1517+
.addReg(0)
1518+
.addReg(ARM::R3));
1519+
}
1520+
1521+
// ldr scratch, [target, #-(PrefixNops * 4 + 4)]
1522+
EmitToStreamer(*OutStreamer,
1523+
MCInstBuilder(ARM::LDRi12)
1524+
.addReg(ScratchReg)
1525+
.addReg(AddrReg)
1526+
.addImm(-(PrefixNops * 4 + 4))
1527+
.addImm(ARMCC::AL)
1528+
.addReg(0));
1529+
1530+
// Each EOR instruction XORs one byte of the type, shifted to its position.
1531+
for (int i = 0; i < 4; i++) {
1532+
uint8_t byte = (Type >> (i * 8)) & 0xFF;
1533+
uint32_t imm = byte << (i * 8);
1534+
bool isLast = (i == 3);
1535+
1536+
// Encode as ARM modified immediate.
1537+
int SOImmVal = ARM_AM::getSOImmVal(imm);
1538+
assert(SOImmVal != -1 && "Cannot encode immediate as ARM modified immediate");
1539+
1540+
// eor[s] scratch, scratch, #imm (last one sets flags with CPSR)
1541+
EmitToStreamer(*OutStreamer,
1542+
MCInstBuilder(ARM::EORri)
1543+
.addReg(ScratchReg)
1544+
.addReg(ScratchReg)
1545+
.addImm(SOImmVal)
1546+
.addImm(ARMCC::AL)
1547+
.addReg(0)
1548+
.addReg(isLast ? ARM::CPSR : 0));
1549+
}
1550+
1551+
// If we spilled r3, restore it immediately after the comparison.
1552+
// This must happen before the branch so r3 is valid on both paths.
1553+
if (NeedSpillR3) {
1554+
// pop {r3}
1555+
EmitToStreamer(*OutStreamer,
1556+
MCInstBuilder(ARM::LDMIA_UPD)
1557+
.addReg(ARM::SP)
1558+
.addReg(ARM::SP)
1559+
.addImm(ARMCC::AL)
1560+
.addReg(0)
1561+
.addReg(ARM::R3));
1562+
}
1563+
1564+
// beq .Lpass (branch if types match, i.e., scratch is zero)
1565+
MCSymbol *Pass = OutContext.createTempSymbol();
1566+
EmitToStreamer(*OutStreamer,
1567+
MCInstBuilder(ARM::Bcc)
1568+
.addExpr(MCSymbolRefExpr::create(Pass, OutContext))
1569+
.addImm(ARMCC::EQ)
1570+
.addReg(ARM::CPSR));
1571+
1572+
// udf #ESR (trap with encoded diagnostic)
1573+
// ESR encoding: 0x8000 | (scratch_reg << 5) | addr_reg.
1574+
// Note: scratch_reg is always 0x1F since the EOR sequence clobbers it
1575+
// and it contains no useful information at trap time.
1576+
const ARMBaseRegisterInfo *TRI =
1577+
static_cast<const ARMBaseRegisterInfo *>(
1578+
MI.getMF()->getSubtarget().getRegisterInfo());
1579+
unsigned AddrIndex = TRI->getEncodingValue(AddrReg);
1580+
unsigned ESR = 0x8000 | (31 << 5) | (AddrIndex & 31);
1581+
1582+
EmitToStreamer(*OutStreamer,
1583+
MCInstBuilder(ARM::UDF)
1584+
.addImm(ESR));
1585+
1586+
OutStreamer->emitLabel(Pass);
1587+
}
1588+
14741589
void ARMAsmPrinter::emitInstruction(const MachineInstr *MI) {
14751590
ARM_MC::verifyInstructionPredicates(MI->getOpcode(),
14761591
getSubtargetInfo().getFeatureBits());
@@ -1504,6 +1619,9 @@ void ARMAsmPrinter::emitInstruction(const MachineInstr *MI) {
15041619
switch (Opc) {
15051620
case ARM::t2MOVi32imm: llvm_unreachable("Should be lowered by thumb2it pass");
15061621
case ARM::DBG_VALUE: llvm_unreachable("Should be handled by generic printing");
1622+
case ARM::KCFI_CHECK:
1623+
LowerKCFI_CHECK(*MI);
1624+
return;
15071625
case ARM::LEApcrel:
15081626
case ARM::tLEApcrel:
15091627
case ARM::t2LEApcrel: {

llvm/lib/Target/ARM/ARMAsmPrinter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ 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

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: 47 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,49 @@ static void genTPLoopBody(MachineBasicBlock *TpLoopBody,
1200712011
.add(predOps(ARMCC::AL));
1200812012
}
1200912013

12014+
bool ARMTargetLowering::supportKCFIBundles() const {
12015+
// KCFI is only supported in ARM mode, not Thumb mode
12016+
return !Subtarget->isThumb();
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+
// KCFI is only supported in ARM mode, not Thumb mode
12027+
assert(!Subtarget->isThumb() && "KCFI not supported in Thumb mode");
12028+
12029+
MachineOperand *TargetOp = nullptr;
12030+
switch (MBBI->getOpcode()) {
12031+
case ARM::BLX:
12032+
case ARM::BLX_pred:
12033+
case ARM::BLX_noip:
12034+
case ARM::BLX_pred_noip:
12035+
case ARM::BX_CALL:
12036+
TargetOp = &MBBI->getOperand(0);
12037+
break;
12038+
case ARM::TCRETURNri:
12039+
case ARM::TCRETURNrinotr12:
12040+
case ARM::TAILJMPr:
12041+
case ARM::TAILJMPr4:
12042+
TargetOp = &MBBI->getOperand(0);
12043+
break;
12044+
default:
12045+
llvm_unreachable("Unexpected CFI call opcode");
12046+
}
12047+
12048+
assert(TargetOp && TargetOp->isReg() && "Invalid target operand");
12049+
TargetOp->setIsRenamable(false);
12050+
12051+
return BuildMI(MBB, MBBI, MBBI->getDebugLoc(), TII->get(ARM::KCFI_CHECK))
12052+
.addReg(TargetOp->getReg())
12053+
.addImm(MBBI->getCFIType())
12054+
.getInstr();
12055+
}
12056+
1201012057
MachineBasicBlock *
1201112058
ARMTargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
1201212059
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6535,6 +6535,14 @@ 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 : PseudoInst<
6543+
(outs), (ins GPR:$ptr, i32imm:$type), NoItinerary, []>, Sched<[]>;
6544+
}
6545+
65386546
//===----------------------------------------------------------------------===//
65396547
// Instructions used for emitting unwind opcodes on Windows.
65406548
//===----------------------------------------------------------------------===//

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/kcfi-arm.ll

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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: ldr r12, [r0, #-4]
10+
; ASM-NEXT: eor r12, r12, #78
11+
; ASM-NEXT: eor r12, r12, #24832
12+
; ASM-NEXT: eor r12, r12, #12320768
13+
; ASM-NEXT: eors r12, r12, #0
14+
; ASM-NEXT: beq .Ltmp{{[0-9]+}}
15+
; UDF encoding: 0x8000 | (0x1F << 5) | r0 = 0x83e0 = 33760
16+
; ASM-NEXT: udf #33760
17+
; ASM-NEXT: .Ltmp{{[0-9]+}}:
18+
; ASM-NEXT: blx r0
19+
20+
; MIR-LABEL: name: f1
21+
; MIR: body:
22+
23+
; ISEL: BLX %0, csr_aapcs,{{.*}} cfi-type 12345678
24+
25+
; KCFI: BUNDLE{{.*}} {
26+
; KCFI-NEXT: KCFI_CHECK $r0, 12345678
27+
; KCFI-NEXT: BLX killed $r0, csr_aapcs,{{.*}}
28+
; KCFI-NEXT: }
29+
30+
call void %x() [ "kcfi"(i32 12345678) ]
31+
ret void
32+
}
33+
34+
; Test with tail call
35+
define void @f2(ptr noundef %x) !kcfi_type !1 {
36+
; ASM-LABEL: f2:
37+
; ASM: @ %bb.0:
38+
; ASM: ldr r12, [r0, #-4]
39+
; ASM: eor r12, r12, #78
40+
; ASM: eor r12, r12, #24832
41+
; ASM: eor r12, r12, #12320768
42+
; ASM: eors r12, r12, #0
43+
; ASM: beq .Ltmp{{[0-9]+}}
44+
; UDF encoding: 0x8000 | (0x1F << 5) | r0 = 0x83e0 = 33760
45+
; ASM: udf #33760
46+
; ASM: .Ltmp{{[0-9]+}}:
47+
; ASM: bx r0
48+
49+
; MIR-LABEL: name: f2
50+
; MIR: body:
51+
52+
; ISEL: TCRETURNri %0, 0, csr_aapcs, implicit $sp, cfi-type 12345678
53+
54+
; KCFI: BUNDLE{{.*}} {
55+
; KCFI-NEXT: KCFI_CHECK $r0, 12345678
56+
; KCFI-NEXT: TAILJMPr killed $r0, csr_aapcs, implicit $sp, implicit $sp
57+
; KCFI-NEXT: }
58+
59+
tail call void %x() [ "kcfi"(i32 12345678) ]
60+
ret void
61+
}
62+
63+
!llvm.module.flags = !{!0}
64+
!0 = !{i32 4, !"kcfi", i32 1}
65+
!1 = !{i32 12345678}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
; RUN: llc -mtriple=armv7-linux-gnueabi -verify-machineinstrs < %s | FileCheck %s
2+
3+
; CHECK: .p2align 2
4+
; CHECK-NOT: nop
5+
; CHECK: .long 12345678
6+
; CHECK-LABEL: f1:
7+
define void @f1(ptr noundef %x) !kcfi_type !1 {
8+
; CHECK: ldr r12, [r0, #-4]
9+
call void %x() [ "kcfi"(i32 12345678) ]
10+
ret void
11+
}
12+
13+
; CHECK: .p2align 2
14+
; CHECK-NOT: .long
15+
; CHECK-NOT: nop
16+
; CHECK-LABEL: f2:
17+
define void @f2(ptr noundef %x) {
18+
; CHECK: ldr r12, [r0, #-4]
19+
call void %x() [ "kcfi"(i32 12345678) ]
20+
ret void
21+
}
22+
23+
; CHECK: .p2align 2
24+
; CHECK: .long 12345678
25+
; CHECK-COUNT-11: nop
26+
; CHECK-LABEL: f3:
27+
define void @f3(ptr noundef %x) #0 !kcfi_type !1 {
28+
; CHECK: ldr r12, [r0, #-48]
29+
call void %x() [ "kcfi"(i32 12345678) ]
30+
ret void
31+
}
32+
33+
; CHECK: .p2align 2
34+
; CHECK-COUNT-11: nop
35+
; CHECK-LABEL: f4:
36+
define void @f4(ptr noundef %x) #0 {
37+
; CHECK: ldr r12, [r0, #-48]
38+
call void %x() [ "kcfi"(i32 12345678) ]
39+
ret void
40+
}
41+
42+
attributes #0 = { "patchable-function-prefix"="11" }
43+
44+
!llvm.module.flags = !{!0}
45+
!0 = !{i32 4, !"kcfi", i32 1}
46+
!1 = !{i32 12345678}

0 commit comments

Comments
 (0)