Index: docs/ControlFlowIntegrity.rst =================================================================== --- /dev/null +++ docs/ControlFlowIntegrity.rst @@ -0,0 +1,34 @@ +====================== +Control Flow Integrity +====================== + +Clang includes an implementation of a number of control flow integrity (CFI) +schemes, which are designed to abort the program upon detecting certain forms +of undefined behavior that can potentially allow attackers to subvert the +program's control flow. These schemes have been optimized for performance, +allowing developers to enable them in release builds. + +To enable Clang's available CFI schemes, use the flag ``-fsanitize=cfi``. +As currently implemented, CFI relies on link-time optimization (LTO); the CFI +schemes imply ``-flto``, and the linker used must support LTO, for example +via the `gold plugin`_. To allow the checks to be implemented efficiently, +the program must be structured such that certain object files are compiled +with CFI enabled, and are statically linked into the program. This may +preclude the use of shared libraries in some cases. + +.. _gold plugin: http://llvm.org/docs/GoldPlugin.html + +CFI for Virtual Calls +--------------------- + +This scheme checks that virtual calls take place using a vptr of the correct +dynamic type; that is, the dynamic type of the called object must be a +derived class of the static type of the object used to make the call. +This CFI scheme can be enabled on its own using ``-fsanitize=cfi-vptr``. + +For this scheme to work, all translation units containing the definition +of a virtual member function (whether inline or not) must be compiled +with ``-fsanitize=cfi-vptr`` enabled and be statically linked into the +program. Classes in the C++ standard library (under namespace ``std``) are +exempted from checking, and therefore programs may be linked against a +regular standard library, but this may change in the future. Index: docs/UsersManual.rst =================================================================== --- docs/UsersManual.rst +++ docs/UsersManual.rst @@ -957,6 +957,8 @@ ``unsigned-integer-overflow`` and ``vptr``. - ``-fsanitize=dataflow``: :doc:`DataFlowSanitizer`, a general data flow analysis. + - ``-fsanitize=cfi``: :doc:`control flow integrity ` + checks. Implies ``-flto``. The following more fine-grained checks are also available: @@ -966,6 +968,8 @@ ``true`` nor ``false``. - ``-fsanitize=bounds``: Out of bounds array indexing, in cases where the array bound can be statically determined. + - ``-fsanitize=cfi-vptr``: Use of an object whose vptr is of the + wrong dynamic type. Implies ``-flto``. - ``-fsanitize=enum``: Load of a value of an enumerated type which is not in the range of representable values for that enumerated type. Index: docs/index.rst =================================================================== --- docs/index.rst +++ docs/index.rst @@ -27,6 +27,7 @@ DataFlowSanitizer LeakSanitizer SanitizerSpecialCaseList + ControlFlowIntegrity Modules MSVCCompatibility FAQ Index: include/clang/AST/Mangle.h =================================================================== --- include/clang/AST/Mangle.h +++ include/clang/AST/Mangle.h @@ -141,6 +141,9 @@ /// across translation units so it can be used with LTO. virtual void mangleTypeName(QualType T, raw_ostream &) = 0; + virtual void mangleCXXVTableBitSet(const CXXRecordDecl *RD, + raw_ostream &) = 0; + /// @} }; Index: include/clang/Basic/Sanitizers.def =================================================================== --- include/clang/Basic/Sanitizers.def +++ include/clang/Basic/Sanitizers.def @@ -76,6 +76,10 @@ // DataFlowSanitizer SANITIZER("dataflow", DataFlow) +// Control Flow Integrity +SANITIZER("cfi-vptr", CFIVptr) +SANITIZER_GROUP("cfi", CFI, CFIVptr) + // -fsanitize=undefined includes all the sanitizers which have low overhead, no // ABI or address space layout implications, and only catch undefined behavior. SANITIZER_GROUP("undefined", Undefined, Index: include/clang/Driver/Driver.h =================================================================== --- include/clang/Driver/Driver.h +++ include/clang/Driver/Driver.h @@ -357,8 +357,8 @@ /// \p Phase on the \p Input, taking in to account arguments /// like -fsyntax-only or --analyze. std::unique_ptr - ConstructPhaseAction(const llvm::opt::ArgList &Args, phases::ID Phase, - std::unique_ptr Input) const; + ConstructPhaseAction(const ToolChain &TC, const llvm::opt::ArgList &Args, + phases::ID Phase, std::unique_ptr Input) const; /// BuildJobsForAction - Construct the jobs to perform for the /// action \p A. @@ -402,7 +402,7 @@ /// handle this action. bool ShouldUseClangCompiler(const JobAction &JA) const; - bool IsUsingLTO(const llvm::opt::ArgList &Args) const; + bool IsUsingLTO(const ToolChain &TC, const llvm::opt::ArgList &Args) const; private: /// \brief Retrieves a ToolChain for a particular target triple. Index: include/clang/Driver/SanitizerArgs.h =================================================================== --- include/clang/Driver/SanitizerArgs.h +++ include/clang/Driver/SanitizerArgs.h @@ -52,6 +52,7 @@ bool sanitizesVptr() const { return Sanitizers.has(SanitizerKind::Vptr); } bool requiresPIE() const; bool needsUnwindTables() const; + bool needsLTO() const; bool linkCXXRuntimes() const { return LinkCXXRuntimes; } void addArgs(const llvm::opt::ArgList &Args, llvm::opt::ArgStringList &CmdArgs) const; Index: lib/AST/ItaniumMangle.cpp =================================================================== --- lib/AST/ItaniumMangle.cpp +++ lib/AST/ItaniumMangle.cpp @@ -164,6 +164,8 @@ void mangleStringLiteral(const StringLiteral *, raw_ostream &) override; + void mangleCXXVTableBitSet(const CXXRecordDecl *RD, raw_ostream &) override; + bool getNextDiscriminator(const NamedDecl *ND, unsigned &disc) { // Lambda closure types are already numbered. if (isLambda(ND)) @@ -3935,6 +3937,18 @@ mangleCXXRTTIName(Ty, Out); } +void ItaniumMangleContextImpl::mangleCXXVTableBitSet(const CXXRecordDecl *RD, + raw_ostream &Out) { + Linkage L = RD->getLinkageInternal(); + if (L == InternalLinkage || L == UniqueExternalLinkage) { + SourceManager &SM = getASTContext().getSourceManager(); + Out << "[" << SM.getFileEntryForID(SM.getMainFileID())->getName() << "]"; + } + + CXXNameMangler Mangler(*this, Out); + Mangler.mangleType(QualType(RD->getTypeForDecl(), 0)); +} + void ItaniumMangleContextImpl::mangleStringLiteral(const StringLiteral *, raw_ostream &) { llvm_unreachable("Can't mangle string literals"); } Index: lib/AST/MicrosoftMangle.cpp =================================================================== --- lib/AST/MicrosoftMangle.cpp +++ lib/AST/MicrosoftMangle.cpp @@ -138,6 +138,8 @@ void mangleSEHFilterExpression(const NamedDecl *EnclosingDecl, raw_ostream &Out) override; void mangleStringLiteral(const StringLiteral *SL, raw_ostream &Out) override; + void mangleCXXVTableBitSet(const CXXRecordDecl *RD, + raw_ostream &Out) override; bool getNextDiscriminator(const NamedDecl *ND, unsigned &disc) { // Lambda closure types are already numbered. if (isLambda(ND)) @@ -2567,6 +2569,11 @@ Mangler.getStream() << '@'; } +void MicrosoftMangleContextImpl::mangleCXXVTableBitSet(const CXXRecordDecl *RD, + raw_ostream &Out) { + llvm_unreachable("Cannot mangle bitsets yet"); +} + MicrosoftMangleContext * MicrosoftMangleContext::create(ASTContext &Context, DiagnosticsEngine &Diags) { return new MicrosoftMangleContextImpl(Context, Diags); Index: lib/CodeGen/CGClass.cpp =================================================================== --- lib/CodeGen/CGClass.cpp +++ lib/CodeGen/CGClass.cpp @@ -24,6 +24,7 @@ #include "clang/Basic/TargetBuiltins.h" #include "clang/CodeGen/CGFunctionInfo.h" #include "clang/Frontend/CodeGenOptions.h" +#include "llvm/IR/Intrinsics.h" using namespace clang; using namespace CodeGen; @@ -2087,6 +2088,38 @@ return VTable; } +void CodeGenFunction::EmitVTablePtrCheckForCall(const CXXMethodDecl *MD, + llvm::Value *VTable) { + if (!SanOpts.has(SanitizerKind::CFIVptr)) + return; + + const CXXRecordDecl *RD = MD->getParent(); + // FIXME: Add blacklisting scheme. + if (RD->isInStdNamespace()) + return; + + std::string OutName; + llvm::raw_string_ostream Out(OutName); + CGM.getCXXABI().getMangleContext().mangleCXXVTableBitSet(RD, Out); + + llvm::Value *BitSetName = llvm::MetadataAsValue::get( + getLLVMContext(), llvm::MDString::get(getLLVMContext(), Out.str())); + + llvm::Value *BitSetTest = Builder.CreateCall2( + CGM.getIntrinsic(llvm::Intrinsic::bitset_test), + Builder.CreateBitCast(VTable, CGM.Int8PtrTy), BitSetName); + + llvm::BasicBlock *ContBlock = createBasicBlock("vtable.check.cont"); + llvm::BasicBlock *TrapBlock = createBasicBlock("vtable.check.trap"); + + Builder.CreateCondBr(BitSetTest, ContBlock, TrapBlock); + + EmitBlock(TrapBlock); + Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::trap)); + Builder.CreateUnreachable(); + + EmitBlock(ContBlock); +} // FIXME: Ideally Expr::IgnoreParenNoopCasts should do this, but it doesn't do // quite what we want. Index: lib/CodeGen/CGVTables.cpp =================================================================== --- lib/CodeGen/CGVTables.cpp +++ lib/CodeGen/CGVTables.cpp @@ -669,6 +669,8 @@ VTLayout->getNumVTableThunks(), RTTI); VTable->setInitializer(Init); + CGM.EmitVTableBitSetEntries(VTable, *VTLayout.get()); + return VTable; } @@ -837,3 +839,73 @@ "deferred extra v-tables during v-table emission?"); DeferredVTables.clear(); } + +void CodeGenModule::EmitVTableBitSetEntries(llvm::GlobalVariable *VTable, + const VTableLayout &VTLayout) { + if (!LangOpts.Sanitize.has(SanitizerKind::CFIVptr)) + return; + + llvm::Metadata *VTableMD = llvm::ConstantAsMetadata::get(VTable); + + std::vector BitsetEntries; + // Create a bit set entry for each address point. + for (auto &&AP : VTLayout.getAddressPoints()) { + // FIXME: Add blacklisting scheme. + if (AP.first.getBase()->isInStdNamespace()) + continue; + + std::string OutName; + llvm::raw_string_ostream Out(OutName); + getCXXABI().getMangleContext().mangleCXXVTableBitSet(AP.first.getBase(), + Out); + + std::vector Elems; + llvm::GlobalVariable *GV = + getModule().getGlobalVariable(Out.str(), true); + if (GV && GV->hasInitializer()) { + auto Arr = dyn_cast(GV->getInitializer()); + if (Arr) { + for (llvm::Value *Op : Arr->operands()) { + Elems.push_back(cast(Op)); + } + } + } + + CharUnits PointerWidth = + Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); + uint64_t AddrPointOffset = AP.second * PointerWidth.getQuantity(); + + llvm::Metadata *BitsetOps[] = { + llvm::MDString::get(getLLVMContext(), Out.str()), + VTableMD, + llvm::ConstantAsMetadata::get( + llvm::ConstantInt::get(Int64Ty, AddrPointOffset))}; + llvm::MDTuple *BitsetEntry = + llvm::MDTuple::get(getLLVMContext(), BitsetOps); + BitsetEntries.push_back(BitsetEntry); + } + + // Sort the bit set entries for determinism. + std::sort(BitsetEntries.begin(), BitsetEntries.end(), [](llvm::MDTuple *T1, + llvm::MDTuple *T2) { + StringRef S1 = cast(T1->getOperand(0))->getString(); + StringRef S2 = cast(T2->getOperand(0))->getString(); + if (S1 < S2) + return true; + if (S1 != S2) + return false; + + uint64_t Offset1 = cast( + cast(T1->getOperand(2)) + ->getValue())->getZExtValue(); + uint64_t Offset2 = cast( + cast(T2->getOperand(2)) + ->getValue())->getZExtValue(); + return Offset1 < Offset2; + }); + + llvm::NamedMDNode *BitsetsMD = + getModule().getOrInsertNamedMetadata("llvm.bitsets"); + for (auto BitsetEntry : BitsetEntries) + BitsetsMD->addOperand(BitsetEntry); +} Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -1319,6 +1319,9 @@ /// to by This. llvm::Value *GetVTablePtr(llvm::Value *This, llvm::Type *Ty); + /// EmitVTablePtrCheckForCall - Virtual method MD is being called via VTable. + /// If vptr CFI is enabled, emit a check that VTable is valid. + void EmitVTablePtrCheckForCall(const CXXMethodDecl *MD, llvm::Value *VTable); /// CanDevirtualizeMemberFunctionCalls - Checks whether virtual calls on given /// expr can be devirtualized. Index: lib/CodeGen/CodeGenModule.h =================================================================== --- lib/CodeGen/CodeGenModule.h +++ lib/CodeGen/CodeGenModule.h @@ -1103,6 +1103,11 @@ /// \param D Threadprivate declaration. void EmitOMPThreadPrivateDecl(const OMPThreadPrivateDecl *D); + /// Emit bit set entries for the given vtable using the given layout if + /// vptr CFI is enabled. + void EmitVTableBitSetEntries(llvm::GlobalVariable *VTable, + const VTableLayout &VTLayout); + private: llvm::Constant * GetOrCreateLLVMFunction(StringRef MangledName, llvm::Type *Ty, GlobalDecl D, Index: lib/CodeGen/ItaniumCXXABI.cpp =================================================================== --- lib/CodeGen/ItaniumCXXABI.cpp +++ lib/CodeGen/ItaniumCXXABI.cpp @@ -1278,6 +1278,8 @@ cast(DC)->getIdentifier()->isStr("__cxxabiv1") && DC->getParent()->isTranslationUnit()) EmitFundamentalRTTIDescriptors(); + + CGM.EmitVTableBitSetEntries(VTable, VTLayout); } llvm::Value *ItaniumCXXABI::getVTableAddressPointInStructor( @@ -1369,6 +1371,8 @@ Ty = Ty->getPointerTo()->getPointerTo(); llvm::Value *VTable = CGF.GetVTablePtr(This, Ty); + CGF.EmitVTablePtrCheckForCall(cast(GD.getDecl()), VTable); + uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); llvm::Value *VFuncPtr = CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); Index: lib/Driver/Driver.cpp =================================================================== --- lib/Driver/Driver.cpp +++ lib/Driver/Driver.cpp @@ -17,6 +17,7 @@ #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Job.h" #include "clang/Driver/Options.h" +#include "clang/Driver/SanitizerArgs.h" #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" #include "llvm/ADT/ArrayRef.h" @@ -1269,7 +1270,7 @@ continue; // Otherwise construct the appropriate action. - Current = ConstructPhaseAction(Args, Phase, std::move(Current)); + Current = ConstructPhaseAction(TC, Args, Phase, std::move(Current)); if (Current->getType() == types::TY_Nothing) break; } @@ -1295,7 +1296,8 @@ } std::unique_ptr -Driver::ConstructPhaseAction(const ArgList &Args, phases::ID Phase, +Driver::ConstructPhaseAction(const ToolChain &TC, const ArgList &Args, + phases::ID Phase, std::unique_ptr Input) const { llvm::PrettyStackTraceString CrashInfo("Constructing phase actions"); // Build the appropriate action. @@ -1354,7 +1356,7 @@ types::TY_LLVM_BC); } case phases::Backend: { - if (IsUsingLTO(Args)) { + if (IsUsingLTO(TC, Args)) { types::ID Output = Args.hasArg(options::OPT_S) ? types::TY_LTO_IR : types::TY_LTO_BC; return llvm::make_unique(std::move(Input), Output); @@ -1375,7 +1377,10 @@ llvm_unreachable("invalid phase in ConstructPhaseAction"); } -bool Driver::IsUsingLTO(const ArgList &Args) const { +bool Driver::IsUsingLTO(const ToolChain &TC, const ArgList &Args) const { + if (TC.getSanitizerArgs().needsLTO()) + return true; + if (Args.hasFlag(options::OPT_flto, options::OPT_fno_lto, false)) return true; Index: lib/Driver/SanitizerArgs.cpp =================================================================== --- lib/Driver/SanitizerArgs.cpp +++ lib/Driver/SanitizerArgs.cpp @@ -47,7 +47,8 @@ SupportsCoverage = Address | Memory | Leak | Undefined | Integer, RecoverableByDefault = Undefined | Integer, Unrecoverable = Address | Unreachable | Return, - LegacyFsanitizeRecoverMask = Undefined | Integer + LegacyFsanitizeRecoverMask = Undefined | Integer, + NeedsLTO = CFIVptr, }; } @@ -148,6 +149,10 @@ return hasOneOf(Sanitizers, NeedsUnwindTables); } +bool SanitizerArgs::needsLTO() const { + return hasOneOf(Sanitizers, CFIVptr); +} + void SanitizerArgs::clear() { Sanitizers.clear(); RecoverableSanitizers.clear(); Index: lib/Driver/Tools.cpp =================================================================== --- lib/Driver/Tools.cpp +++ lib/Driver/Tools.cpp @@ -5794,7 +5794,8 @@ // If we are using LTO, then automatically create a temporary file path for // the linker to use, so that it's lifetime will extend past a possible // dsymutil step. - if (Version[0] >= 116 && D.IsUsingLTO(Args) && NeedsTempPath(Inputs)) { + if (Version[0] >= 116 && D.IsUsingLTO(getToolChain(), Args) && + NeedsTempPath(Inputs)) { const char *TmpPath = C.getArgs().MakeArgString( D.GetTemporaryPath("cc", types::getTypeTempSuffix(types::TY_Object))); C.addTempFile(TmpPath); @@ -6811,7 +6812,7 @@ Args.AddAllArgs(CmdArgs, options::OPT_Z_Flag); Args.AddAllArgs(CmdArgs, options::OPT_r); - if (D.IsUsingLTO(Args)) + if (D.IsUsingLTO(getToolChain(), Args)) AddGoldPlugin(ToolChain, Args, CmdArgs); bool NeedsSanitizerDeps = addSanitizerRuntimes(ToolChain, Args, CmdArgs); @@ -7654,7 +7655,7 @@ for (const auto &Path : Paths) CmdArgs.push_back(Args.MakeArgString(StringRef("-L") + Path)); - if (D.IsUsingLTO(Args)) + if (D.IsUsingLTO(getToolChain(), Args)) AddGoldPlugin(ToolChain, Args, CmdArgs); if (Args.hasArg(options::OPT_Z_Xlinker__no_demangle)) Index: test/CodeGenCXX/cfi-vptr.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/cfi-vptr.cpp @@ -0,0 +1,74 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vptr -emit-llvm -o - %s | FileCheck %s + +struct A { + A(); + virtual void f(); +}; + +struct B : virtual A { + B(); +}; + +struct C : virtual A { + C(); +}; + +namespace { + +struct D : B, C { + D(); + virtual void f(); +}; + +} + +A::A() {} +B::B() {} +C::C() {} +D::D() {} + +void A::f() { +} + +void D::f() { +} + +// CHECK: define void @_Z2afP1A +void af(A *a) { + // CHECK: [[P:%[^ ]*]] = call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"1A") + // CHECK-NEXT: br i1 [[P]], label %[[CONTBB:[^ ]*]], label %[[TRAPBB:[^ ]*]] + + // CHECK: [[TRAPBB]] + // CHECK-NEXT: call void @llvm.trap() + // CHECK-NEXT: unreachable + + // CHECK: [[CONTBB]] + // CHECK: call void % + a->f(); +} + +// CHECK: define internal void @_Z2dfPN12_GLOBAL__N_11DE +void df(D *d) { + // CHECK: {{%[^ ]*}} = call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"[{{.*}}cfi-vptr.cpp]N12_GLOBAL__N_11DE") + d->f(); +} + +D d; + +void foo() { + df(&d); +} + +// CHECK-DAG: !{!"1A", [3 x i8*]* @_ZTV1A, i64 16} +// CHECK-DAG: !{!"1A", [5 x i8*]* @_ZTCN12_GLOBAL__N_11DE0_1B, i64 32} +// CHECK-DAG: !{!"1B", [5 x i8*]* @_ZTCN12_GLOBAL__N_11DE0_1B, i64 32} +// CHECK-DAG: !{!"1A", [9 x i8*]* @_ZTCN12_GLOBAL__N_11DE8_1C, i64 64} +// CHECK-DAG: !{!"1C", [9 x i8*]* @_ZTCN12_GLOBAL__N_11DE8_1C, i64 32} +// CHECK-DAG: !{!"1A", [10 x i8*]* @_ZTVN12_GLOBAL__N_11DE, i64 32} +// CHECK-DAG: !{!"1B", [10 x i8*]* @_ZTVN12_GLOBAL__N_11DE, i64 32} +// CHECK-DAG: !{!"1C", [10 x i8*]* @_ZTVN12_GLOBAL__N_11DE, i64 72} +// CHECK-DAG: !{!"[{{.*}}cfi-vptr.cpp]N12_GLOBAL__N_11DE", [10 x i8*]* @_ZTVN12_GLOBAL__N_11DE, i64 32} +// CHECK-DAG: !{!"1A", [5 x i8*]* @_ZTV1B, i64 32} +// CHECK-DAG: !{!"1B", [5 x i8*]* @_ZTV1B, i64 32} +// CHECK-DAG: !{!"1A", [5 x i8*]* @_ZTV1C, i64 32} +// CHECK-DAG: !{!"1C", [5 x i8*]* @_ZTV1C, i64 32} Index: test/Driver/fsanitize.c =================================================================== --- test/Driver/fsanitize.c +++ test/Driver/fsanitize.c @@ -186,6 +186,10 @@ // RUN: %clang -target x86_64-apple-darwin10 -fsanitize=function -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-FSAN-UBSAN-DARWIN // CHECK-FSAN-UBSAN-DARWIN: unsupported option '-fsanitize=function' for target 'x86_64-apple-darwin10' +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-vptr -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI +// CHECK-CFI: -emit-llvm-bc{{.*}}-fsanitize=cfi-vptr + // RUN: %clang_cl -fsanitize=address -c -MDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL // RUN: %clang_cl -fsanitize=address -c -MTd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL // RUN: %clang_cl -fsanitize=address -c -LDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL