diff --git a/docs/ControlFlowIntegrity.rst b/docs/ControlFlowIntegrity.rst index 51c99177674e7c59c9f39ef58ec55f8d2d445831..915385b7b197cd22f3b6be039747108da7ee239e 100644 --- a/docs/ControlFlowIntegrity.rst +++ b/docs/ControlFlowIntegrity.rst @@ -27,8 +27,8 @@ 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. -Clang currently implements forward-edge CFI for virtual calls. More schemes -are under development. +Clang currently implements forward-edge CFI for member function calls and +bad cast checking. More schemes are under development. .. _gold plugin: http://llvm.org/docs/GoldPlugin.html @@ -38,11 +38,11 @@ Forward-Edge 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``. +This CFI scheme can be enabled on its own using ``-fsanitize=cfi-vcall``. 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 +with ``-fsanitize=cfi-vcall`` 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 pre-built standard library, but this may change in the future. @@ -95,6 +95,23 @@ and be statically linked into the program. Classes in the C++ standard library may be linked against a pre-built standard library, but this may change in the future. +Non-Virtual Member Function Call Checking +----------------------------------------- + +This scheme checks that non-virtual calls take place using an object 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. The checks are currently only introduced where the object is of a +polymorphic class type. This CFI scheme can be enabled on its own using +``-fsanitize=cfi-nvcall``. + +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-nvcall`` 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 +pre-built standard library, but this may change in the future. + .. _cfi-strictness: Strictness diff --git a/docs/UsersManual.rst b/docs/UsersManual.rst index 29c4d4ef90199a192dc1dde4812eff17d69eb718..bf8ce78036f2570fe77f0e8c69134f63d45afbf0 100644 --- a/docs/UsersManual.rst +++ b/docs/UsersManual.rst @@ -974,7 +974,9 @@ are listed below. dynamic type. Implies ``-flto``. - ``-fsanitize=cfi-unrelated-cast``: Cast from ``void*`` or another unrelated type to the wrong dynamic type. Implies ``-flto``. - - ``-fsanitize=cfi-vptr``: Use of an object whose vptr is of the + - ``-fsanitize=cfi-nvcall``: Non-virtual call via an object whose vptr is of + the wrong dynamic type. Implies ``-flto``. + - ``-fsanitize=cfi-vcall``: Virtual call via 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 diff --git a/include/clang/Basic/Sanitizers.def b/include/clang/Basic/Sanitizers.def index fa58a34a034bfcbdc3ca186d10ce415d80a9a7dc..65ababd5ac8679f264aba5cc01475038a43273d9 100644 --- a/include/clang/Basic/Sanitizers.def +++ b/include/clang/Basic/Sanitizers.def @@ -82,8 +82,10 @@ SANITIZER("dataflow", DataFlow) SANITIZER("cfi-cast-strict", CFICastStrict) SANITIZER("cfi-derived-cast", CFIDerivedCast) SANITIZER("cfi-unrelated-cast", CFIUnrelatedCast) -SANITIZER("cfi-vptr", CFIVptr) -SANITIZER_GROUP("cfi", CFI, CFIDerivedCast | CFIUnrelatedCast | CFIVptr) +SANITIZER("cfi-nvcall", CFINVCall) +SANITIZER("cfi-vcall", CFIVCall) +SANITIZER_GROUP("cfi", CFI, + CFIDerivedCast | CFIUnrelatedCast | CFINVCall | CFIVCall) // -fsanitize=undefined-trap includes sanitizers from -fsanitize=undefined // that can be used without runtime support, generally by providing extra diff --git a/lib/CodeGen/CGClass.cpp b/lib/CodeGen/CGClass.cpp index 84d6437abbecacc2b9ed6523d4e0afdb1bbb7562..bd15c1210950588615db93c95b717355a9992be3 100644 --- a/lib/CodeGen/CGClass.cpp +++ b/lib/CodeGen/CGClass.cpp @@ -2088,14 +2088,6 @@ llvm::Value *CodeGenFunction::GetVTablePtr(llvm::Value *This, return VTable; } -void CodeGenFunction::EmitVTablePtrCheckForCall(const CXXMethodDecl *MD, - llvm::Value *VTable) { - if (!SanOpts.has(SanitizerKind::CFIVptr)) - return; - - EmitVTablePtrCheck(MD->getParent(), VTable); -} - // If a class has a single non-virtual base and does not introduce or override // virtual member functions or fields, it will have the same layout as its base. // This function returns the least derived such class. @@ -2131,6 +2123,15 @@ LeastDerivedClassWithSameLayout(const CXXRecordDecl *RD) { RD->bases_begin()->getType()->getAsCXXRecordDecl()); } +void CodeGenFunction::EmitVTablePtrCheckForCall(const CXXMethodDecl *MD, + llvm::Value *VTable) { + const CXXRecordDecl *ClassDecl = MD->getParent(); + if (!SanOpts.has(SanitizerKind::CFICastStrict)) + ClassDecl = LeastDerivedClassWithSameLayout(ClassDecl); + + EmitVTablePtrCheck(ClassDecl, VTable); +} + void CodeGenFunction::EmitVTablePtrCheckForCast(QualType T, llvm::Value *Derived, bool MayBeNull) { diff --git a/lib/CodeGen/CGExprCXX.cpp b/lib/CodeGen/CGExprCXX.cpp index 6852d3afc7fb47878dcec9317c95baeff7c8319c..f7bf40660c851413294e2e27ab21722f5d2bf4b4 100644 --- a/lib/CodeGen/CGExprCXX.cpp +++ b/lib/CodeGen/CGExprCXX.cpp @@ -256,6 +256,12 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr( } else if (UseVirtualCall) { Callee = CGM.getCXXABI().getVirtualFunctionPointer(*this, MD, This, Ty); } else { + if (SanOpts.has(SanitizerKind::CFINVCall) && + MD->getParent()->isDynamicClass()) { + llvm::Value *VTable = GetVTablePtr(This, Int8PtrTy); + EmitVTablePtrCheckForCall(MD, VTable); + } + if (getLangOpts().AppleKext && MD->isVirtual() && HasQualifier) Callee = BuildAppleKextVirtualCall(MD, Qualifier, Ty); else if (!DevirtualizedMethod) diff --git a/lib/CodeGen/CGVTables.cpp b/lib/CodeGen/CGVTables.cpp index 372db7a7f6ece0a4ed3846bc6a8995da320cb1de..57370a6faa2c0817affeaf4629e275404ab560b9 100644 --- a/lib/CodeGen/CGVTables.cpp +++ b/lib/CodeGen/CGVTables.cpp @@ -842,7 +842,10 @@ void CodeGenModule::EmitDeferredVTables() { void CodeGenModule::EmitVTableBitSetEntries(llvm::GlobalVariable *VTable, const VTableLayout &VTLayout) { - if (!LangOpts.Sanitize.has(SanitizerKind::CFIVptr)) + if (!LangOpts.Sanitize.has(SanitizerKind::CFIVCall) && + !LangOpts.Sanitize.has(SanitizerKind::CFINVCall) && + !LangOpts.Sanitize.has(SanitizerKind::CFIDerivedCast) && + !LangOpts.Sanitize.has(SanitizerKind::CFIUnrelatedCast)) return; llvm::Metadata *VTableMD = llvm::ConstantAsMetadata::get(VTable); diff --git a/lib/CodeGen/ItaniumCXXABI.cpp b/lib/CodeGen/ItaniumCXXABI.cpp index 62f1293ff66753f32671eeee4b0334bbd13cbb35..7bb0a9bafcbe31f5c2c96e820665be2342d25ec1 100644 --- a/lib/CodeGen/ItaniumCXXABI.cpp +++ b/lib/CodeGen/ItaniumCXXABI.cpp @@ -1443,7 +1443,8 @@ llvm::Value *ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF, Ty = Ty->getPointerTo()->getPointerTo(); llvm::Value *VTable = CGF.GetVTablePtr(This, Ty); - CGF.EmitVTablePtrCheckForCall(cast<CXXMethodDecl>(GD.getDecl()), VTable); + if (CGF.SanOpts.has(SanitizerKind::CFIVCall)) + CGF.EmitVTablePtrCheckForCall(cast<CXXMethodDecl>(GD.getDecl()), VTable); uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); llvm::Value *VFuncPtr = diff --git a/lib/Driver/SanitizerArgs.cpp b/lib/Driver/SanitizerArgs.cpp index 88725cdedd260de9b925d57063f53d6ed687b95a..cd3785cd90b9d6d5d59a29340aacdb5e27ba1a22 100644 --- a/lib/Driver/SanitizerArgs.cpp +++ b/lib/Driver/SanitizerArgs.cpp @@ -48,7 +48,7 @@ enum SanitizeKind : uint64_t { RecoverableByDefault = Undefined | Integer, Unrecoverable = Address | Unreachable | Return, LegacyFsanitizeRecoverMask = Undefined | Integer, - NeedsLTO = CFIDerivedCast | CFIUnrelatedCast | CFIVptr, + NeedsLTO = CFI, }; } diff --git a/test/CodeGenCXX/cfi-nvcall.cpp b/test/CodeGenCXX/cfi-nvcall.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0db478c9d0cbf84cd2432d8a91283f7c294562c --- /dev/null +++ b/test/CodeGenCXX/cfi-nvcall.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-nvcall -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-nvcall,cfi-cast-strict -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-STRICT %s + +struct A { + virtual void f(); +}; + +struct B : A { + int i; + void g(); +}; + +struct C : A { + void g(); +}; + +// CHECK-LABEL: @bg +// CHECK-STRICT-LABEL: @bg +extern "C" void bg(B *b) { + // CHECK: call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"1B") + // CHECK-STRICT: call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"1B") + b->g(); +} + +// CHECK-LABEL: @cg +// CHECK-STRICT-LABEL: @cg +extern "C" void cg(C *c) { + // http://clang.llvm.org/docs/ControlFlowIntegrity.html#strictness + // In this case C's layout is the same as its base class, so we allow + // c to be of type A in non-strict mode. + + // CHECK: call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"1A") + // CHECK-STRICT: call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"1C") + c->g(); +} diff --git a/test/CodeGenCXX/cfi-vptr.cpp b/test/CodeGenCXX/cfi-vcall.cpp similarity index 88% rename from test/CodeGenCXX/cfi-vptr.cpp rename to test/CodeGenCXX/cfi-vcall.cpp index 545f22c3c7202a8e25521f402e6ca34f6828f8ae..bfbbceaa1a38313c7322f2e7512e7fcdedf767d2 100644 --- a/test/CodeGenCXX/cfi-vptr.cpp +++ b/test/CodeGenCXX/cfi-vcall.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vptr -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall -emit-llvm -o - %s | FileCheck %s struct A { A(); @@ -49,7 +49,7 @@ void af(A *a) { // 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") + // CHECK: {{%[^ ]*}} = call i1 @llvm.bitset.test(i8* {{%[^ ]*}}, metadata !"[{{.*}}cfi-vcall.cpp]N12_GLOBAL__N_11DE") d->f(); } @@ -67,7 +67,7 @@ void foo() { // 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: !{!"[{{.*}}cfi-vcall.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} diff --git a/test/Driver/fsanitize.c b/test/Driver/fsanitize.c index f43029bba74fb6b5e029d77cd0301a31bb497d65..994b2b236c4f408965740c2f9ebfe8e02c67f8d8 100644 --- a/test/Driver/fsanitize.c +++ b/test/Driver/fsanitize.c @@ -206,11 +206,13 @@ // 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-derived-cast -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-DCAST // RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-unrelated-cast -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-UCAST -// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-vptr -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-VPTR -// CHECK-CFI: -emit-llvm-bc{{.*}}-fsanitize=cfi-derived-cast,cfi-unrelated-cast,cfi-vptr +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-nvcall -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-NVCALL +// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi-vcall -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-VCALL +// CHECK-CFI: -emit-llvm-bc{{.*}}-fsanitize=cfi-derived-cast,cfi-unrelated-cast,cfi-nvcall,cfi-vcall // CHECK-CFI-DCAST: -emit-llvm-bc{{.*}}-fsanitize=cfi-derived-cast // CHECK-CFI-UCAST: -emit-llvm-bc{{.*}}-fsanitize=cfi-unrelated-cast -// CHECK-CFI-VPTR: -emit-llvm-bc{{.*}}-fsanitize=cfi-vptr +// CHECK-CFI-NVCALL: -emit-llvm-bc{{.*}}-fsanitize=cfi-nvcall +// CHECK-CFI-VCALL: -emit-llvm-bc{{.*}}-fsanitize=cfi-vcall // 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