Skip to content

Commit eb2a57e

Browse files
committed
Add generic KCFI operand bundle lowering
The KCFI sanitizer emits "kcfi" operand bundles to indirect call instructions, which the LLVM back-end lowers into an architecture-specific type check with a known machine instruction sequence. Currently, KCFI operand bundle lowering is supported only on 64-bit X86 and AArch64 architectures. As a lightweight forward-edge CFI implementation that doesn't require LTO is also useful for non-Linux low-level targets on other machine architectures, add a generic KCFI operand bundle lowering pass that's only used when back-end lowering support is not available and allows -fsanitize=kcfi to be enabled in Clang on all architectures. Reviewed By: nickdesaulniers, MaskRay Differential Revision: https://reviews.llvm.org/D135411
1 parent c4436f6 commit eb2a57e

File tree

12 files changed

+232
-5
lines changed

12 files changed

+232
-5
lines changed

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
#include "llvm/Transforms/Instrumentation/GCOVProfiler.h"
7373
#include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h"
7474
#include "llvm/Transforms/Instrumentation/InstrProfiling.h"
75+
#include "llvm/Transforms/Instrumentation/KCFI.h"
7576
#include "llvm/Transforms/Instrumentation/MemProfiler.h"
7677
#include "llvm/Transforms/Instrumentation/MemorySanitizer.h"
7778
#include "llvm/Transforms/Instrumentation/SanitizerBinaryMetadata.h"
@@ -644,6 +645,31 @@ static OptimizationLevel mapToLevel(const CodeGenOptions &Opts) {
644645
}
645646
}
646647

648+
static void addKCFIPass(TargetMachine *TM, const Triple &TargetTriple,
649+
const LangOptions &LangOpts, PassBuilder &PB) {
650+
// If the back-end supports KCFI operand bundle lowering, skip KCFIPass.
651+
if (TargetTriple.getArch() == llvm::Triple::x86_64 ||
652+
TargetTriple.isAArch64(64))
653+
return;
654+
655+
// Ensure we lower KCFI operand bundles with -O0.
656+
PB.registerOptimizerLastEPCallback(
657+
[&, TM](ModulePassManager &MPM, OptimizationLevel Level) {
658+
if (Level == OptimizationLevel::O0 &&
659+
LangOpts.Sanitize.has(SanitizerKind::KCFI))
660+
MPM.addPass(createModuleToFunctionPassAdaptor(KCFIPass(TM)));
661+
});
662+
663+
// When optimizations are requested, run KCIFPass after InstCombine to
664+
// avoid unnecessary checks.
665+
PB.registerPeepholeEPCallback(
666+
[&, TM](FunctionPassManager &FPM, OptimizationLevel Level) {
667+
if (Level != OptimizationLevel::O0 &&
668+
LangOpts.Sanitize.has(SanitizerKind::KCFI))
669+
FPM.addPass(KCFIPass(TM));
670+
});
671+
}
672+
647673
static void addSanitizers(const Triple &TargetTriple,
648674
const CodeGenOptions &CodeGenOpts,
649675
const LangOptions &LangOpts, PassBuilder &PB) {
@@ -946,8 +972,10 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
946972

947973
// Don't add sanitizers if we are here from ThinLTO PostLink. That already
948974
// done on PreLink stage.
949-
if (!IsThinLTOPostLink)
975+
if (!IsThinLTOPostLink) {
950976
addSanitizers(TargetTriple, CodeGenOpts, LangOpts, PB);
977+
addKCFIPass(TM.get(), TargetTriple, LangOpts, PB);
978+
}
951979

952980
if (Optional<GCOVOptions> Options = getGCOVOptions(CodeGenOpts, LangOpts))
953981
PB.registerPipelineStartEPCallback(

clang/lib/Driver/ToolChain.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,17 +1089,14 @@ SanitizerMask ToolChain::getSupportedSanitizers() const {
10891089
~SanitizerKind::Function) |
10901090
(SanitizerKind::CFI & ~SanitizerKind::CFIICall) |
10911091
SanitizerKind::CFICastStrict | SanitizerKind::FloatDivideByZero |
1092-
SanitizerKind::UnsignedIntegerOverflow |
1092+
SanitizerKind::KCFI | SanitizerKind::UnsignedIntegerOverflow |
10931093
SanitizerKind::UnsignedShiftBase | SanitizerKind::ImplicitConversion |
10941094
SanitizerKind::Nullability | SanitizerKind::LocalBounds;
10951095
if (getTriple().getArch() == llvm::Triple::x86 ||
10961096
getTriple().getArch() == llvm::Triple::x86_64 ||
10971097
getTriple().getArch() == llvm::Triple::arm || getTriple().isWasm() ||
10981098
getTriple().isAArch64() || getTriple().isRISCV())
10991099
Res |= SanitizerKind::CFIICall;
1100-
if (getTriple().getArch() == llvm::Triple::x86_64 ||
1101-
getTriple().isAArch64(64))
1102-
Res |= SanitizerKind::KCFI;
11031100
if (getTriple().getArch() == llvm::Triple::x86_64 ||
11041101
getTriple().isAArch64(64) || getTriple().isRISCV())
11051102
Res |= SanitizerKind::ShadowCallStack;

llvm/include/llvm/InitializePasses.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ void initializeLowerInvokeLegacyPassPass(PassRegistry&);
258258
void initializeLowerSwitchLegacyPassPass(PassRegistry &);
259259
void initializeLowerMatrixIntrinsicsLegacyPassPass(PassRegistry &);
260260
void initializeLowerMatrixIntrinsicsMinimalLegacyPassPass(PassRegistry &);
261+
void initializeKCFIPass(PassRegistry &);
261262
void initializeMIRAddFSDiscriminatorsPass(PassRegistry &);
262263
void initializeMIRCanonicalizerPass(PassRegistry &);
263264
void initializeMIRNamerPass(PassRegistry &);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===-- KCFI.h - Generic KCFI operand bundle lowering -----------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This pass emits generic KCFI indirect call checks for targets that don't
10+
// support lowering KCFI operand bundles in the back-end.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_KCFI_H
15+
#define LLVM_TRANSFORMS_INSTRUMENTATION_KCFI_H
16+
17+
#include "llvm/IR/PassManager.h"
18+
19+
namespace llvm {
20+
class TargetMachine;
21+
22+
class KCFIPass : public PassInfoMixin<KCFIPass> {
23+
TargetMachine *TM;
24+
25+
public:
26+
KCFIPass(TargetMachine *TM) : TM(TM) {}
27+
static bool isRequired() { return true; }
28+
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
29+
};
30+
} // namespace llvm
31+
#endif // LLVM_TRANSFORMS_INSTRUMENTATION_KCFI_H

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
#include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h"
138138
#include "llvm/Transforms/Instrumentation/InstrOrderFile.h"
139139
#include "llvm/Transforms/Instrumentation/InstrProfiling.h"
140+
#include "llvm/Transforms/Instrumentation/KCFI.h"
140141
#include "llvm/Transforms/Instrumentation/MemProfiler.h"
141142
#include "llvm/Transforms/Instrumentation/MemorySanitizer.h"
142143
#include "llvm/Transforms/Instrumentation/PGOInstrumentation.h"

llvm/lib/Passes/PassRegistry.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ FUNCTION_PASS("nary-reassociate", NaryReassociatePass())
322322
FUNCTION_PASS("newgvn", NewGVNPass())
323323
FUNCTION_PASS("jump-threading", JumpThreadingPass())
324324
FUNCTION_PASS("partially-inline-libcalls", PartiallyInlineLibCallsPass())
325+
FUNCTION_PASS("kcfi", KCFIPass(TM))
325326
FUNCTION_PASS("lcssa", LCSSAPass())
326327
FUNCTION_PASS("loop-data-prefetch", LoopDataPrefetchPass())
327328
FUNCTION_PASS("loop-load-elim", LoopLoadEliminationPass())

llvm/lib/Transforms/Instrumentation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_llvm_component_library(LLVMInstrumentation
1111
Instrumentation.cpp
1212
InstrOrderFile.cpp
1313
InstrProfiling.cpp
14+
KCFI.cpp
1415
PGOInstrumentation.cpp
1516
PGOMemOPSizeOpt.cpp
1617
PoisonChecking.cpp
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===-- KCFI.cpp - Generic KCFI operand bundle lowering ---------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This pass emits generic KCFI indirect call checks for targets that don't
10+
// support lowering KCFI operand bundles in the back-end.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "llvm/Transforms/Instrumentation/KCFI.h"
15+
#include "llvm/ADT/Statistic.h"
16+
#include "llvm/CodeGen/TargetLowering.h"
17+
#include "llvm/CodeGen/TargetSubtargetInfo.h"
18+
#include "llvm/IR/Constants.h"
19+
#include "llvm/IR/DiagnosticInfo.h"
20+
#include "llvm/IR/DiagnosticPrinter.h"
21+
#include "llvm/IR/Function.h"
22+
#include "llvm/IR/GlobalObject.h"
23+
#include "llvm/IR/IRBuilder.h"
24+
#include "llvm/IR/InstIterator.h"
25+
#include "llvm/IR/Instructions.h"
26+
#include "llvm/IR/Intrinsics.h"
27+
#include "llvm/IR/MDBuilder.h"
28+
#include "llvm/IR/Module.h"
29+
#include "llvm/InitializePasses.h"
30+
#include "llvm/Pass.h"
31+
#include "llvm/Target/TargetMachine.h"
32+
#include "llvm/Transforms/Instrumentation.h"
33+
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
34+
35+
using namespace llvm;
36+
37+
#define DEBUG_TYPE "kcfi"
38+
39+
STATISTIC(NumKCFIChecks, "Number of kcfi operands transformed into checks");
40+
41+
namespace {
42+
class DiagnosticInfoKCFI : public DiagnosticInfo {
43+
const Twine &Msg;
44+
45+
public:
46+
DiagnosticInfoKCFI(const Twine &DiagMsg,
47+
DiagnosticSeverity Severity = DS_Error)
48+
: DiagnosticInfo(DK_Linker, Severity), Msg(DiagMsg) {}
49+
void print(DiagnosticPrinter &DP) const override { DP << Msg; }
50+
};
51+
} // namespace
52+
53+
PreservedAnalyses KCFIPass::run(Function &F, FunctionAnalysisManager &AM) {
54+
Module &M = *F.getParent();
55+
if (!M.getModuleFlag("kcfi") ||
56+
(TM &&
57+
TM->getSubtargetImpl(F)->getTargetLowering()->supportKCFIBundles()))
58+
return PreservedAnalyses::all();
59+
60+
// Find call instructions with KCFI operand bundles.
61+
SmallVector<CallInst *> KCFICalls;
62+
for (Instruction &I : instructions(F)) {
63+
if (auto *CI = dyn_cast<CallInst>(&I))
64+
if (CI->getOperandBundle(LLVMContext::OB_kcfi))
65+
KCFICalls.push_back(CI);
66+
}
67+
68+
if (KCFICalls.empty())
69+
return PreservedAnalyses::all();
70+
71+
LLVMContext &Ctx = M.getContext();
72+
// patchable-function-prefix emits nops between the KCFI type identifier
73+
// and the function start. As we don't know the size of the emitted nops,
74+
// don't allow this attribute with generic lowering.
75+
if (F.hasFnAttribute("patchable-function-prefix"))
76+
Ctx.diagnose(
77+
DiagnosticInfoKCFI("-fpatchable-function-entry=N,M, where M>0 is not "
78+
"compatible with -fsanitize=kcfi on this target"));
79+
80+
IntegerType *Int32Ty = Type::getInt32Ty(Ctx);
81+
MDNode *VeryUnlikelyWeights =
82+
MDBuilder(Ctx).createBranchWeights(1, (1U << 20) - 1);
83+
84+
for (CallInst *CI : KCFICalls) {
85+
// Get the expected hash value.
86+
const uint32_t ExpectedHash =
87+
cast<ConstantInt>(CI->getOperandBundle(LLVMContext::OB_kcfi)->Inputs[0])
88+
->getZExtValue();
89+
90+
// Drop the KCFI operand bundle.
91+
CallBase *Call =
92+
CallBase::removeOperandBundle(CI, LLVMContext::OB_kcfi, CI);
93+
assert(Call != CI);
94+
Call->copyMetadata(*CI);
95+
CI->replaceAllUsesWith(Call);
96+
CI->eraseFromParent();
97+
98+
if (!Call->isIndirectCall())
99+
continue;
100+
101+
// Emit a check and trap if the target hash doesn't match.
102+
IRBuilder<> Builder(Call);
103+
Value *HashPtr = Builder.CreateConstInBoundsGEP1_32(
104+
Int32Ty, Call->getCalledOperand(), -1);
105+
Value *Test = Builder.CreateICmpNE(Builder.CreateLoad(Int32Ty, HashPtr),
106+
ConstantInt::get(Int32Ty, ExpectedHash));
107+
Instruction *ThenTerm =
108+
SplitBlockAndInsertIfThen(Test, Call, false, VeryUnlikelyWeights);
109+
Builder.SetInsertPoint(ThenTerm);
110+
Builder.CreateCall(Intrinsic::getDeclaration(&M, Intrinsic::trap));
111+
++NumKCFIChecks;
112+
}
113+
114+
return PreservedAnalyses::none();
115+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
; RUN: not opt -S -passes=kcfi %s 2>&1 | FileCheck %s
2+
3+
; CHECK: error: -fpatchable-function-entry=N,M, where M>0 is not compatible with -fsanitize=kcfi on this target
4+
define void @f1(ptr noundef %x) #0 {
5+
call void %x() [ "kcfi"(i32 12345678) ]
6+
ret void
7+
}
8+
9+
attributes #0 = { "patchable-function-prefix"="1" }
10+
11+
!llvm.module.flags = !{!0}
12+
!0 = !{i32 4, !"kcfi", i32 1}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
; REQUIRES: x86-registered-target
2+
; RUN: opt -S -passes=kcfi %s | FileCheck %s
3+
4+
;; If the back-end supports KCFI operand bundle lowering, KCFIPass should be a no-op.
5+
6+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
7+
target triple = "x86_64-unknown-linux-gnu"
8+
9+
; CHECK-LABEL: define void @f1
10+
define void @f1(ptr noundef %x) {
11+
; CHECK-NOT: call void @llvm.trap()
12+
; CHECK: call void %x() [ "kcfi"(i32 12345678) ]
13+
call void %x() [ "kcfi"(i32 12345678) ]
14+
ret void
15+
}
16+
17+
!llvm.module.flags = !{!0}
18+
!0 = !{i32 4, !"kcfi", i32 1}

0 commit comments

Comments
 (0)