Skip to content

Commit

Permalink
[cfi] Cross-DSO CFI diagnostic mode (clang part)
Browse files Browse the repository at this point in the history
* Runtime diagnostic data for cfi-icall changed to match the rest of
  cfi checks
* Layout of all CFI diagnostic data changed to put Kind at the
  beginning. There is no ABI stability promise yet.
* Call cfi_slowpath_diag instead of cfi_slowpath when needed.
* Emit __cfi_check_fail function, which dispatches a CFI check
  faliure according to trap/recover settings of the current module.
* A tiny driver change to match the way the new handlers are done in
  compiler-rt.

llvm-svn: 258745
  • Loading branch information
eugenis committed Jan 25, 2016
1 parent 2269652 commit 3fd61df
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 56 deletions.
23 changes: 13 additions & 10 deletions clang/lib/CodeGen/CGClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,8 @@ void CodeGenFunction::EmitVTablePtrCheck(const CXXRecordDecl *RD,
case CFITCK_UnrelatedCast:
SSK = llvm::SanStat_CFI_UnrelatedCast;
break;
case CFITCK_ICall:
llvm_unreachable("not expecting CFITCK_ICall");
}
EmitSanitizerStatReport(SSK);

Expand All @@ -2578,13 +2580,6 @@ void CodeGenFunction::EmitVTablePtrCheck(const CXXRecordDecl *RD,
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::bitset_test),
{CastedVTable, BitSetName});

if (CGM.getCodeGenOpts().SanitizeCfiCrossDso) {
if (auto TypeId = CGM.CreateCfiIdForTypeMetadata(MD)) {
EmitCfiSlowPathCheck(BitSetTest, TypeId, CastedVTable);
return;
}
}

SanitizerMask M;
switch (TCK) {
case CFITCK_VCall:
Expand All @@ -2599,15 +2594,23 @@ void CodeGenFunction::EmitVTablePtrCheck(const CXXRecordDecl *RD,
case CFITCK_UnrelatedCast:
M = SanitizerKind::CFIUnrelatedCast;
break;
case CFITCK_ICall:
llvm_unreachable("not expecting CFITCK_ICall");
}

llvm::Constant *StaticData[] = {
llvm::ConstantInt::get(Int8Ty, TCK),
EmitCheckSourceLocation(Loc),
EmitCheckTypeDescriptor(QualType(RD->getTypeForDecl(), 0)),
llvm::ConstantInt::get(Int8Ty, TCK),
};
EmitCheck(std::make_pair(BitSetTest, M), "cfi_bad_type", StaticData,
CastedVTable);

auto TypeId = CGM.CreateCfiIdForTypeMetadata(MD);
if (CGM.getCodeGenOpts().SanitizeCfiCrossDso && TypeId) {
EmitCfiSlowPathCheck(M, BitSetTest, TypeId, CastedVTable, StaticData);
} else {
EmitCheck(std::make_pair(BitSetTest, M), "cfi_check_fail", StaticData,
CastedVTable);
}
}

// FIXME: Ideally Expr::IgnoreParenNoopCasts should do this, but it doesn't do
Expand Down
166 changes: 132 additions & 34 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc,

if (Checks.size() > 0) {
llvm::Constant *StaticData[] = {
EmitCheckSourceLocation(Loc),
EmitCheckSourceLocation(Loc),
EmitCheckTypeDescriptor(Ty),
llvm::ConstantInt::get(SizeTy, AlignVal),
llvm::ConstantInt::get(Int8Ty, TCK)
Expand Down Expand Up @@ -2466,12 +2466,16 @@ void CodeGenFunction::EmitCheck(
assert(JointCond);

CheckRecoverableKind RecoverKind = getRecoverableKind(Checked[0].second);
assert(SanOpts.has(Checked[0].second));
// In cross-DSO CFI mode this code is used to generate __cfi_check_fail, which
// includes all checks, even those that are not in SanOpts.
assert(CGM.getCodeGenOpts().SanitizeCfiCrossDso ||
SanOpts.has(Checked[0].second));
#ifndef NDEBUG
for (int i = 1, n = Checked.size(); i < n; ++i) {
assert(RecoverKind == getRecoverableKind(Checked[i].second) &&
"All recoverable kinds in a single check must be same!");
assert(SanOpts.has(Checked[i].second));
assert(CGM.getCodeGenOpts().SanitizeCfiCrossDso ||
SanOpts.has(Checked[i].second));
}
#endif

Expand All @@ -2485,24 +2489,26 @@ void CodeGenFunction::EmitCheck(
Branch->setMetadata(llvm::LLVMContext::MD_prof, Node);
EmitBlock(Handlers);

// Emit handler arguments and create handler function type.
llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs);
auto *InfoPtr =
new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false,
llvm::GlobalVariable::PrivateLinkage, Info);
InfoPtr->setUnnamedAddr(true);
CGM.getSanitizerMetadata()->disableSanitizerForGlobal(InfoPtr);

// Handler functions take an i8* pointing to the (handler-specific) static
// information block, followed by a sequence of intptr_t arguments
// representing operand values.
SmallVector<llvm::Value *, 4> Args;
SmallVector<llvm::Type *, 4> ArgTypes;
Args.reserve(DynamicArgs.size() + 1);
ArgTypes.reserve(DynamicArgs.size() + 1);

// Handler functions take an i8* pointing to the (handler-specific) static
// information block, followed by a sequence of intptr_t arguments
// representing operand values.
Args.push_back(Builder.CreateBitCast(InfoPtr, Int8PtrTy));
ArgTypes.push_back(Int8PtrTy);
// Emit handler arguments and create handler function type.
if (!StaticArgs.empty()) {
llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs);
auto *InfoPtr =
new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false,
llvm::GlobalVariable::PrivateLinkage, Info);
InfoPtr->setUnnamedAddr(true);
CGM.getSanitizerMetadata()->disableSanitizerForGlobal(InfoPtr);
Args.push_back(Builder.CreateBitCast(InfoPtr, Int8PtrTy));
ArgTypes.push_back(Int8PtrTy);
}

for (size_t i = 0, n = DynamicArgs.size(); i != n; ++i) {
Args.push_back(EmitCheckValue(DynamicArgs[i]));
ArgTypes.push_back(IntPtrTy);
Expand Down Expand Up @@ -2534,10 +2540,9 @@ void CodeGenFunction::EmitCheck(
EmitBlock(Cont);
}

void CodeGenFunction::EmitCfiSlowPathCheck(llvm::Value *Cond,
llvm::ConstantInt *TypeId,
llvm::Value *Ptr) {
auto &Ctx = getLLVMContext();
void CodeGenFunction::EmitCfiSlowPathCheck(
SanitizerMask Kind, llvm::Value *Cond, llvm::ConstantInt *TypeId,
llvm::Value *Ptr, ArrayRef<llvm::Constant *> StaticArgs) {
llvm::BasicBlock *Cont = createBasicBlock("cfi.cont");

llvm::BasicBlock *CheckBB = createBasicBlock("cfi.slowpath");
Expand All @@ -2549,19 +2554,110 @@ void CodeGenFunction::EmitCfiSlowPathCheck(llvm::Value *Cond,

EmitBlock(CheckBB);

llvm::Constant *SlowPathFn = CGM.getModule().getOrInsertFunction(
"__cfi_slowpath",
llvm::FunctionType::get(
llvm::Type::getVoidTy(Ctx),
{llvm::Type::getInt64Ty(Ctx),
llvm::PointerType::getUnqual(llvm::Type::getInt8Ty(Ctx))},
false));
llvm::CallInst *CheckCall = Builder.CreateCall(SlowPathFn, {TypeId, Ptr});
bool WithDiag = !CGM.getCodeGenOpts().SanitizeTrap.has(Kind);

llvm::CallInst *CheckCall;
if (WithDiag) {
llvm::Constant *Info = llvm::ConstantStruct::getAnon(StaticArgs);
auto *InfoPtr =
new llvm::GlobalVariable(CGM.getModule(), Info->getType(), false,
llvm::GlobalVariable::PrivateLinkage, Info);
InfoPtr->setUnnamedAddr(true);
CGM.getSanitizerMetadata()->disableSanitizerForGlobal(InfoPtr);

llvm::Constant *SlowPathDiagFn = CGM.getModule().getOrInsertFunction(
"__cfi_slowpath_diag",
llvm::FunctionType::get(VoidTy, {Int64Ty, Int8PtrTy, Int8PtrTy},
false));
CheckCall = Builder.CreateCall(
SlowPathDiagFn,
{TypeId, Ptr, Builder.CreateBitCast(InfoPtr, Int8PtrTy)});
} else {
llvm::Constant *SlowPathFn = CGM.getModule().getOrInsertFunction(
"__cfi_slowpath",
llvm::FunctionType::get(VoidTy, {Int64Ty, Int8PtrTy}, false));
CheckCall = Builder.CreateCall(SlowPathFn, {TypeId, Ptr});
}

CheckCall->setDoesNotThrow();

EmitBlock(Cont);
}

// This function is basically a switch over the CFI failure kind, which is
// extracted from CFICheckFailData (1st function argument). Each case is either
// llvm.trap or a call to one of the two runtime handlers, based on
// -fsanitize-trap and -fsanitize-recover settings. Default case (invalid
// failure kind) traps, but this should really never happen. CFICheckFailData
// can be nullptr if the calling module has -fsanitize-trap behavior for this
// check kind; in this case __cfi_check_fail traps as well.
void CodeGenFunction::EmitCfiCheckFail() {
SanitizerScope SanScope(this);
FunctionArgList Args;
ImplicitParamDecl ArgData(getContext(), nullptr, SourceLocation(), nullptr,
getContext().VoidPtrTy);
ImplicitParamDecl ArgAddr(getContext(), nullptr, SourceLocation(), nullptr,
getContext().VoidPtrTy);
Args.push_back(&ArgData);
Args.push_back(&ArgAddr);

const CGFunctionInfo &FI = CGM.getTypes().arrangeFreeFunctionDeclaration(
getContext().VoidTy, Args, FunctionType::ExtInfo(), /*variadic=*/false);

llvm::Function *F = llvm::Function::Create(
llvm::FunctionType::get(VoidTy, {VoidPtrTy, VoidPtrTy}, false),
llvm::GlobalValue::WeakODRLinkage, "__cfi_check_fail", &CGM.getModule());
F->setVisibility(llvm::GlobalValue::HiddenVisibility);

StartFunction(GlobalDecl(), CGM.getContext().VoidTy, F, FI, Args,
SourceLocation());

llvm::Value *Data =
EmitLoadOfScalar(GetAddrOfLocalVar(&ArgData), /*Volatile=*/false,
CGM.getContext().VoidPtrTy, ArgData.getLocation());
llvm::Value *Addr =
EmitLoadOfScalar(GetAddrOfLocalVar(&ArgAddr), /*Volatile=*/false,
CGM.getContext().VoidPtrTy, ArgAddr.getLocation());

// Data == nullptr means the calling module has trap behaviour for this check.
llvm::Value *DataIsNotNullPtr =
Builder.CreateICmpNE(Data, llvm::ConstantPointerNull::get(Int8PtrTy));
EmitTrapCheck(DataIsNotNullPtr);

llvm::StructType *SourceLocationTy =
llvm::StructType::get(VoidPtrTy, Int32Ty, Int32Ty, nullptr);
llvm::StructType *CfiCheckFailDataTy =
llvm::StructType::get(Int8Ty, SourceLocationTy, VoidPtrTy, nullptr);

llvm::Value *V = Builder.CreateConstGEP2_32(
CfiCheckFailDataTy,
Builder.CreatePointerCast(Data, CfiCheckFailDataTy->getPointerTo(0)), 0,
0);
Address CheckKindAddr(V, getIntAlign());
llvm::Value *CheckKind = Builder.CreateLoad(CheckKindAddr);

constexpr std::pair<int, SanitizerMask> CheckKinds[] = {
{CFITCK_VCall, SanitizerKind::CFIVCall},
{CFITCK_NVCall, SanitizerKind::CFINVCall},
{CFITCK_DerivedCast, SanitizerKind::CFIDerivedCast},
{CFITCK_UnrelatedCast, SanitizerKind::CFIUnrelatedCast},
{CFITCK_ICall, SanitizerKind::CFIICall}};

SmallVector<std::pair<llvm::Value *, SanitizerMask>, 5> Checks;
for (auto CheckKindMaskPair : CheckKinds) {
int Kind = CheckKindMaskPair.first;
SanitizerMask Mask = CheckKindMaskPair.second;
llvm::Value *Cond =
Builder.CreateICmpNE(CheckKind, llvm::ConstantInt::get(Int8Ty, Kind));
EmitCheck(std::make_pair(Cond, Mask), "cfi_check_fail", {}, {Data, Addr});
}

FinishFunction();
// The only reference to this function will be created during LTO link.
// Make sure it survives until then.
CGM.addUsedGlobal(F);
}

void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked) {
llvm::BasicBlock *Cont = createBasicBlock("cont");

Expand Down Expand Up @@ -3864,15 +3960,17 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, llvm::Value *Callee,
{CastedCallee, BitSetName});

auto TypeId = CGM.CreateCfiIdForTypeMetadata(MD);
llvm::Constant *StaticData[] = {
llvm::ConstantInt::get(Int8Ty, CFITCK_ICall),
EmitCheckSourceLocation(E->getLocStart()),
EmitCheckTypeDescriptor(QualType(FnType, 0)),
};
if (CGM.getCodeGenOpts().SanitizeCfiCrossDso && TypeId) {
EmitCfiSlowPathCheck(BitSetTest, TypeId, CastedCallee);
EmitCfiSlowPathCheck(SanitizerKind::CFIICall, BitSetTest, TypeId,
CastedCallee, StaticData);
} else {
llvm::Constant *StaticData[] = {
EmitCheckSourceLocation(E->getLocStart()),
EmitCheckTypeDescriptor(QualType(FnType, 0)),
};
EmitCheck(std::make_pair(BitSetTest, SanitizerKind::CFIICall),
"cfi_bad_icall", StaticData, CastedCallee);
"cfi_check_fail", StaticData, CastedCallee);
}
}

Expand Down
9 changes: 7 additions & 2 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,7 @@ class CodeGenFunction : public CodeGenTypeCache {
CFITCK_NVCall,
CFITCK_DerivedCast,
CFITCK_UnrelatedCast,
CFITCK_ICall,
};

/// \brief Derived is the presumed address of an object of type T after a
Expand Down Expand Up @@ -3018,8 +3019,9 @@ class CodeGenFunction : public CodeGenTypeCache {

/// \brief Emit a slow path cross-DSO CFI check which calls __cfi_slowpath
/// if Cond if false.
void EmitCfiSlowPathCheck(llvm::Value *Cond, llvm::ConstantInt *TypeId,
llvm::Value *Ptr);
void EmitCfiSlowPathCheck(SanitizerMask Kind, llvm::Value *Cond,
llvm::ConstantInt *TypeId, llvm::Value *Ptr,
ArrayRef<llvm::Constant *> StaticArgs);

/// \brief Create a basic block that will call the trap intrinsic, and emit a
/// conditional branch to it, for the -ftrapv checks.
Expand All @@ -3029,6 +3031,9 @@ class CodeGenFunction : public CodeGenTypeCache {
/// "trap-func-name" if specified.
llvm::CallInst *EmitTrapCall(llvm::Intrinsic::ID IntrID);

/// \brief Emit a cross-DSO CFI failure handling function.
void EmitCfiCheckFail();

/// \brief Create a check for a function parameter that may potentially be
/// declared as non-null.
void EmitNonNullArgCheck(RValue RV, QualType ArgType, SourceLocation ArgLoc,
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ void CodeGenModule::Release() {
EmitDeferredUnusedCoverageMappings();
if (CoverageMapping)
CoverageMapping->emit();
if (CodeGenOpts.SanitizeCfiCrossDso)
CodeGenFunction(*this).EmitCfiCheckFail();
emitLLVMUsed();
if (SanStats)
SanStats->finish();
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Driver/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2871,8 +2871,11 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
StaticRuntimes.push_back("safestack");
if (SanArgs.needsCfiRt())
StaticRuntimes.push_back("cfi");
if (SanArgs.needsCfiDiagRt())
if (SanArgs.needsCfiDiagRt()) {
StaticRuntimes.push_back("cfi_diag");
if (SanArgs.linkCXXRuntimes())
StaticRuntimes.push_back("ubsan_standalone_cxx");
}
if (SanArgs.needsStatsRt()) {
NonWholeStaticRuntimes.push_back("stats");
RequiredSymbols.push_back("__sanitizer_stats_register");
Expand Down
71 changes: 71 additions & 0 deletions clang/test/CodeGen/cfi-check-fail.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux -O0 -fsanitize=cfi-icall -fsanitize-cfi-cross-dso \
// RUN: -fsanitize-trap=cfi-icall,cfi-nvcall -fsanitize-recover=cfi-vcall,cfi-unrelated-cast \
// RUN: -emit-llvm -o - %s | FileCheck %s

void caller(void (*f)()) {
f();
}

// CHECK: define weak_odr hidden void @__cfi_check_fail(i8*, i8*) {
// CHECK: store i8* %0, i8** %[[ALLOCA0:.*]], align 8
// CHECK: store i8* %1, i8** %[[ALLOCA1:.*]], align 8
// CHECK: %[[DATA:.*]] = load i8*, i8** %[[ALLOCA0]], align 8
// CHECK: %[[ADDR:.*]] = load i8*, i8** %[[ALLOCA1]], align 8
// CHECK: %[[ICMP_NOT_NULL:.*]] = icmp ne i8* %[[DATA]], null
// CHECK: br i1 %[[ICMP_NOT_NULL]], label %[[CONT0:.*]], label %[[TRAP:.*]],

// CHECK: [[TRAP]]
// CHECK-NEXT: call void @llvm.trap()
// CHECK-NEXT: unreachable

// CHECK: [[CONT0]]
// CHECK: %[[A:.*]] = bitcast i8* %[[DATA]] to { i8, { i8*, i32, i32 }, i8* }*
// CHECK: %[[KINDPTR:.*]] = getelementptr {{.*}} %[[A]], i32 0, i32 0
// CHECK: %[[KIND:.*]] = load i8, i8* %[[KINDPTR]], align 4
// CHECK: %[[NOT_0:.*]] = icmp ne i8 %[[KIND]], 0
// CHECK: br i1 %[[NOT_0]], label %[[CONT1:.*]], label %[[HANDLE0:.*]], !prof

// CHECK: [[HANDLE0]]
// CHECK: %[[DATA0:.*]] = ptrtoint i8* %[[DATA]] to i64,
// CHECK: %[[ADDR0:.*]] = ptrtoint i8* %[[ADDR]] to i64,
// CHECK: call void @__ubsan_handle_cfi_check_fail(i64 %[[DATA0]], i64 %[[ADDR0]])
// CHECK: br label %[[CONT1]]

// CHECK: [[CONT1]]
// CHECK: %[[NOT_1:.*]] = icmp ne i8 %[[KIND]], 1
// CHECK: br i1 %[[NOT_1]], label %[[CONT2:.*]], label %[[HANDLE1:.*]], !nosanitize

// CHECK: [[HANDLE1]]
// CHECK-NEXT: call void @llvm.trap()
// CHECK-NEXT: unreachable

// CHECK: [[CONT2]]
// CHECK: %[[NOT_2:.*]] = icmp ne i8 %[[KIND]], 2
// CHECK: br i1 %[[NOT_2]], label %[[CONT3:.*]], label %[[HANDLE2:.*]], !prof

// CHECK: [[HANDLE2]]
// CHECK: %[[DATA2:.*]] = ptrtoint i8* %[[DATA]] to i64,
// CHECK: %[[ADDR2:.*]] = ptrtoint i8* %[[ADDR]] to i64,
// CHECK: call void @__ubsan_handle_cfi_check_fail_abort(i64 %[[DATA2]], i64 %[[ADDR2]])
// CHECK: unreachable

// CHECK: [[CONT3]]
// CHECK: %[[NOT_3:.*]] = icmp ne i8 %[[KIND]], 3
// CHECK: br i1 %[[NOT_3]], label %[[CONT4:.*]], label %[[HANDLE3:.*]], !prof

// CHECK: [[HANDLE3]]
// CHECK: %[[DATA3:.*]] = ptrtoint i8* %[[DATA]] to i64,
// CHECK: %[[ADDR3:.*]] = ptrtoint i8* %[[ADDR]] to i64,
// CHECK: call void @__ubsan_handle_cfi_check_fail(i64 %[[DATA3]], i64 %[[ADDR3]])
// CHECK: br label %[[CONT4]]

// CHECK: [[CONT4]]
// CHECK: %[[NOT_4:.*]] = icmp ne i8 %[[KIND]], 4
// CHECK: br i1 %[[NOT_4]], label %[[CONT5:.*]], label %[[HANDLE4:.*]], !nosanitize

// CHECK: [[HANDLE4]]
// CHECK-NEXT: call void @llvm.trap()
// CHECK-NEXT: unreachable

// CHECK: [[CONT5]]
// CHECK: ret void
Loading

0 comments on commit 3fd61df

Please sign in to comment.