Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -97,6 +97,7 @@ void initializeMachineCopyPropagationPass(PassRegistry&); void initializeCostModelAnalysisPass(PassRegistry&); void initializeCorrelatedValuePropagationPass(PassRegistry&); +void initializeCrossDSOCFIPass(PassRegistry&); void initializeDAEPass(PassRegistry&); void initializeDAHPass(PassRegistry&); void initializeDCEPass(PassRegistry&); Index: include/llvm/Transforms/IPO.h =================================================================== --- include/llvm/Transforms/IPO.h +++ include/llvm/Transforms/IPO.h @@ -210,6 +210,9 @@ /// to bitsets. ModulePass *createLowerBitSetsPass(); +/// \brief This pass export CFI checks for use by external modules. +ModulePass *createCrossDSOCFIPass(); + //===----------------------------------------------------------------------===// // SampleProfilePass - Loads sample profile data from disk and generates // IR metadata to reflect the profile. Index: include/llvm/Transforms/IPO/CrossDSOCFI.h =================================================================== --- /dev/null +++ include/llvm/Transforms/IPO/CrossDSOCFI.h @@ -0,0 +1,25 @@ +//===- CrossDSOCFI.h------------------------ --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines common control flow integrity checker utilities. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_IPO_CROSSDSOCFI_H +#define LLVM_TRANSFORMS_IPO_CROSSDSOCFI_H + +#include "llvm/Support/MD5.h" + +namespace llvm { + +uint64_t BuildCFICallSiteTypeId(StringRef S); + +} + +#endif // LLVM_TRANSFORMS_IPO_CROSSDSOCFI_H Index: lib/Transforms/IPO/CMakeLists.txt =================================================================== --- lib/Transforms/IPO/CMakeLists.txt +++ lib/Transforms/IPO/CMakeLists.txt @@ -2,6 +2,7 @@ ArgumentPromotion.cpp BarrierNoopPass.cpp ConstantMerge.cpp + CrossDSOCFI.cpp DeadArgumentElimination.cpp ElimAvailExtern.cpp ExtractGV.cpp Index: lib/Transforms/IPO/CrossDSOCFI.cpp =================================================================== --- /dev/null +++ lib/Transforms/IPO/CrossDSOCFI.cpp @@ -0,0 +1,168 @@ +//===-- CrossDSOCFI.cpp - Externalize this modules' CFI checks ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// FIXME description +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/IPO/CrossDSOCFI.h" + +#include "llvm/Transforms/IPO.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/EquivalenceClasses.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/IR/Constant.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalObject.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/MDBuilder.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Operator.h" +#include "llvm/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" + +using namespace llvm; + +#define DEBUG_TYPE "cross-dso-cfi" + +STATISTIC(TypeIds, "Number of unique type identifiers"); + +uint64_t llvm::BuildCFICallSiteTypeId(StringRef S) { + MD5 md5; + MD5::MD5Result result; + md5.update(S); + md5.final(result); + uint64_t id; + memcpy(&id, &result, sizeof(id)); + return id; +} + +namespace { + +struct CrossDSOCFI : public ModulePass { + static char ID; + CrossDSOCFI() : ModulePass(ID) { + initializeCrossDSOCFIPass(*PassRegistry::getPassRegistry()); + } + + Module *M; + MDNode *VeryLikelyWeights; + + static uint64_t getCallSiteTypeId(MDString *MDS); + bool buildCFICheck(); + + bool doInitialization(Module &M) override; + bool runOnModule(Module &M) override; +}; + +} // anonymous namespace + +INITIALIZE_PASS_BEGIN(CrossDSOCFI, "cross-dso-cfi", "Cross-DSO CFI", false, + false) +INITIALIZE_PASS_END(CrossDSOCFI, "cross-dso-cfi", "Cross-DSO CFI", false, false) +char CrossDSOCFI::ID = 0; + +ModulePass *llvm::createCrossDSOCFIPass() { return new CrossDSOCFI; } + +bool CrossDSOCFI::doInitialization(Module &Mod) { + M = &Mod; + VeryLikelyWeights = + MDBuilder(M->getContext()).createBranchWeights((1U << 20) - 1, 1); + + return false; +} + +bool CrossDSOCFI::buildCFICheck() { + // FIXME: handle hash collisions + // FIXME: insert at the end of the module to push it closer to the RO segment? + llvm::DenseSet BitSetIds; + NamedMDNode *BitSetNM = M->getNamedMetadata("llvm.bitsets"); + + if (BitSetNM) { + for (unsigned I = 0, E = BitSetNM->getNumOperands(); I != E; ++I) { + MDNode *Op = BitSetNM->getOperand(I); + auto V = dyn_cast_or_null(Op->getOperand(1)); + if (!V) + continue; + auto F = dyn_cast(V->getValue()); + // Don't emit a check if it's just a declaration. + if (F && F->isDeclaration()) + continue; + // This check excludes vtables for classes inside anonymous namespaces. + auto S = dyn_cast(Op->getOperand(0)); + if (!S) + continue; + BitSetIds.insert(S); + } + } + + LLVMContext &Ctx = M->getContext(); + Constant *C = M->getOrInsertFunction( + "__cfi_check", + FunctionType::get( + Type::getVoidTy(Ctx), + {Type::getInt64Ty(Ctx), PointerType::getUnqual(Type::getInt8Ty(Ctx))}, + false)); + Function *F = dyn_cast(C); + F->setAlignment(4096); + auto args = F->arg_begin(); + Argument &Arg0 = *(args++); + Value *TypeId = dyn_cast(&Arg0); + TypeId->setName("CallSiteTypeId"); + Argument &Arg1 = *(args++); + Value *Addr = dyn_cast(&Arg1); + Addr->setName("Addr"); + assert(args == F->arg_end()); + + BasicBlock *BB = BasicBlock::Create(Ctx, "entry", F); + + BasicBlock *TrapBB = BasicBlock::Create(Ctx, "trap", F); + IRBuilder<> IRBTrap(TrapBB); + Function *TrapFn = Intrinsic::getDeclaration(M, Intrinsic::trap); + llvm::CallInst *TrapCall = IRBTrap.CreateCall(TrapFn); + TrapCall->setDoesNotReturn(); + TrapCall->setDoesNotThrow(); + IRBTrap.CreateUnreachable(); + + BasicBlock *ExitBB = BasicBlock::Create(Ctx, "exit", F); + IRBuilder<> IRBExit(ExitBB); + IRBExit.CreateRetVoid(); + + IRBuilder<> IRB(BB); + SwitchInst *SI = IRB.CreateSwitch(TypeId, TrapBB, BitSetIds.size()); + for (MDString *MDS : BitSetIds) { + BasicBlock *TestBB = BasicBlock::Create(Ctx, "test." + MDS->getString(), F); + IRBuilder<> IRBTest(TestBB); + uint64_t CaseTypeId = BuildCFICallSiteTypeId(MDS->getString()); + Function *BitsetTestFn = + Intrinsic::getDeclaration(M, Intrinsic::bitset_test); + Value *Test = IRBTest.CreateCall(BitsetTestFn, + {Addr, MetadataAsValue::get(Ctx, MDS)}); + BranchInst *BI = IRBTest.CreateCondBr(Test, ExitBB, TrapBB); + BI->setMetadata(LLVMContext::MD_prof, VeryLikelyWeights); + + SI->addCase(ConstantInt::get(Type::getInt64Ty(Ctx), CaseTypeId), TestBB); + ++TypeIds; + } + + return true; +} + +bool CrossDSOCFI::runOnModule(Module &M) { + if (M.getModuleFlag("Cross-DSO CFI") == nullptr) + return false; + buildCFICheck(); + return true; +} Index: lib/Transforms/IPO/IPO.cpp =================================================================== --- lib/Transforms/IPO/IPO.cpp +++ lib/Transforms/IPO/IPO.cpp @@ -24,6 +24,7 @@ void llvm::initializeIPO(PassRegistry &Registry) { initializeArgPromotionPass(Registry); initializeConstantMergePass(Registry); + initializeCrossDSOCFIPass(Registry); initializeDAEPass(Registry); initializeDAHPass(Registry); initializeFunctionAttrsPass(Registry); Index: lib/Transforms/IPO/PassManagerBuilder.cpp =================================================================== --- lib/Transforms/IPO/PassManagerBuilder.cpp +++ lib/Transforms/IPO/PassManagerBuilder.cpp @@ -595,6 +595,9 @@ if (OptLevel > 1) addLTOOptimizationPasses(PM); + // Control flow integrity for cross-DSO calls. + PM.add(createCrossDSOCFIPass()); + // Lower bit sets to globals. This pass supports Clang's control flow // integrity mechanisms (-fsanitize=cfi*) and needs to run at link time if CFI // is enabled. The pass does nothing if CFI is disabled. Index: test/Transforms/CrossDSOCFI/basic.ll =================================================================== --- /dev/null +++ test/Transforms/CrossDSOCFI/basic.ll @@ -0,0 +1,109 @@ +; RUN: opt -S -cross-dso-cfi < %s | FileCheck %s + +; CHECK: define void @__cfi_check(i64 {{.*}}, i8* {{.*}}) align 4096 +; CHECK: switch i64{{.*}} label %[[TRAP:.*]] [ +; CHECK: i64 {{.*}}, label +; CHECK: i64 {{.*}}, label +; CHECK: i64 {{.*}}, label +; CHECK: i64 {{.*}}, label + +; CHECK: [[TRAP]]: +; CHECK: call void @llvm.trap() +; CHECK: unreachable + +; CHECK: [[EXIT:.*]]: +; CHECK: ret void + +; CHECK: call i1 @llvm.bitset.test +; CHECK: br {{.*}} label %[[EXIT]], label %[[TRAP]] + +; CHECK: call i1 @llvm.bitset.test +; CHECK: br {{.*}} label %[[EXIT]], label %[[TRAP]] + +; CHECK: call i1 @llvm.bitset.test +; CHECK: br {{.*}} label %[[EXIT]], label %[[TRAP]] + +; CHECK: call i1 @llvm.bitset.test +; CHECK: br {{.*}} label %[[EXIT]], label %[[TRAP]] + +; ModuleID = 'lib6.so.bc' +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%class.A = type { i32 (...)** } +%class.B = type { %class.A } + +@_ZTV1A = unnamed_addr constant [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast (void (%class.A*)* @_ZN1A1fEv to i8*)], align 8 +@_ZTI1A = constant { i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv117__class_type_infoE, i64 2) to i8*), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) } +@_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +@_ZTS1A = constant [3 x i8] c"1A\00" +@_ZTV1B = unnamed_addr constant [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8*, i8* }* @_ZTI1B to i8*), i8* bitcast (void (%class.B*)* @_ZN1B1fEv to i8*)], align 8 +@_ZTI1B = constant { i8*, i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2) to i8*), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i32 0, i32 0), i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*) } +@_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8* +@_ZTS1B = constant [3 x i8] c"1B\00" + +; Function Attrs: norecurse nounwind readnone uwtable +define void @_ZN1A1fEv(%class.A* nocapture %this) unnamed_addr #0 align 2 { +entry: + ret void +} + +; Function Attrs: norecurse nounwind readnone uwtable +define void @_ZN1B1fEv(%class.B* nocapture %this) unnamed_addr #0 align 2 { +entry: + ret void +} + +; Function Attrs: norecurse nounwind readnone uwtable +define signext i8 @f11() #0 { +entry: + ret i8 1 +} + +; Function Attrs: norecurse nounwind readnone uwtable +define signext i8 @f12() #0 { +entry: + ret i8 2 +} + +; Function Attrs: norecurse nounwind readnone uwtable +define signext i8 @f13() #0 { +entry: + ret i8 3 +} + +; Function Attrs: norecurse nounwind readnone uwtable +define i32 @f21() #0 { +entry: + ret i32 4 +} + +; Function Attrs: norecurse nounwind readnone uwtable +define i32 @f22() #0 { +entry: + ret i32 5 +} + + +attributes #0 = { norecurse nounwind readnone uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { nounwind readnone } +attributes #3 = { nounwind } + +!llvm.bitsets = !{!0, !1, !2, !3, !4, !7, !8, !9} +!llvm.ident = !{!10} +!llvm.module.flags = !{!11, !12} + +!0 = !{!"_ZTSFcvE", i8 ()* @f11, i64 0} +!1 = !{!"_ZTSFcvE", i8 ()* @f12, i64 0} +!2 = !{!"_ZTSFcvE", i8 ()* @f13, i64 0} +!3 = !{!"_ZTSFivE", i32 ()* @f21, i64 0} +!4 = !{!"_ZTSFivE", i32 ()* @f22, i64 0} +!7 = !{!"_ZTS1A", [3 x i8*]* @_ZTV1A, i64 16} +!8 = !{!"_ZTS1A", [3 x i8*]* @_ZTV1B, i64 16} +!9 = !{!"_ZTS1B", [3 x i8*]* @_ZTV1B, i64 16} +!10 = !{!"clang version 3.8.0 (trunk 254754) (llvm/trunk 254764)"} +!11 = !{i32 4, !"cross-dso-cfi", i32 1} +!12 = !{i32 1, !"PIC Level", i32 2} +!13 = !{} +!14 = !{!"branch_weights", i32 1048575, i32 1}