From 175d633271c3b76535474168522578ca244aae5d Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Fri, 19 Jun 2015 01:52:55 +0000 Subject: [PATCH] Add control flow integrity diagnosis function to UBSan runtime library. Also includes execution tests for the feature. Differential Revision: http://reviews.llvm.org/D10269 llvm-svn: 240111 --- compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc | 39 ++++++++++++++++++++++++++- compiler-rt/lib/ubsan/ubsan_handlers_cxx.h | 13 +++++++++ compiler-rt/lib/ubsan/ubsan_type_hash.cc | 21 ++++++++++----- compiler-rt/lib/ubsan/ubsan_type_hash.h | 5 +++- compiler-rt/test/cfi/CMakeLists.txt | 1 + compiler-rt/test/cfi/anon-namespace.cpp | 9 +++++++ compiler-rt/test/cfi/bad-cast.cpp | 13 +++++++++ compiler-rt/test/cfi/lit.cfg | 4 ++- compiler-rt/test/cfi/multiple-inheritance.cpp | 8 ++++++ compiler-rt/test/cfi/nvcall.cpp | 9 +++++-- compiler-rt/test/cfi/overwrite.cpp | 9 +++++-- compiler-rt/test/cfi/simple-fail.cpp | 7 +++++ compiler-rt/test/cfi/vdtor.cpp | 5 ++++ 13 files changed, 129 insertions(+), 14 deletions(-) diff --git a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc index b3cda32..83b9939 100644 --- a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc +++ b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc @@ -37,7 +37,7 @@ static void HandleDynamicTypeCacheMiss( return; // Check if error report should be suppressed. - DynamicTypeInfo DTI = getDynamicTypeInfo((void*)Pointer); + DynamicTypeInfo DTI = getDynamicTypeInfoFromObject((void*)Pointer); if (DTI.isValid() && IsVptrCheckSuppressed(DTI.getMostDerivedTypeName())) return; @@ -82,4 +82,41 @@ void __ubsan::__ubsan_handle_dynamic_type_cache_miss_abort( HandleDynamicTypeCacheMiss(Data, Pointer, Hash, Opts); } +static void HandleCFIBadType(CFIBadTypeData *Data, ValueHandle Vtable, + ReportOptions Opts) { + SourceLocation Loc = Data->Loc.acquire(); + ScopedReport R(Opts, Loc); + DynamicTypeInfo DTI = getDynamicTypeInfoFromVtable((void*)Vtable); + + static const char *TypeCheckKinds[] = { + "virtual call", + "non-virtual call", + "base-to-derived cast", + "cast to unrelated type", + }; + + Diag(Loc, DL_Error, "control flow integrity check for type %0 failed during " + "%1 (vtable address %2)") + << Data->Type << TypeCheckKinds[Data->TypeCheckKind] << (void *)Vtable; + + // If possible, say what type it actually points to. + if (!DTI.isValid()) + Diag(Vtable, DL_Note, "invalid vtable"); + else + Diag(Vtable, DL_Note, "vtable is of type %0") + << MangledName(DTI.getMostDerivedTypeName()); +} + +void __ubsan::__ubsan_handle_cfi_bad_type(CFIBadTypeData *Data, + ValueHandle Vtable) { + GET_REPORT_OPTIONS(false); + HandleCFIBadType(Data, Vtable, Opts); +} + +void __ubsan::__ubsan_handle_cfi_bad_type_abort(CFIBadTypeData *Data, + ValueHandle Vtable) { + GET_REPORT_OPTIONS(true); + HandleCFIBadType(Data, Vtable, Opts); +} + #endif // CAN_SANITIZE_UB diff --git a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.h b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.h index cb1bca7..92050d99 100644 --- a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.h +++ b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.h @@ -25,6 +25,12 @@ struct DynamicTypeCacheMissData { unsigned char TypeCheckKind; }; +struct CFIBadTypeData { + SourceLocation Loc; + const TypeDescriptor &Type; + unsigned char TypeCheckKind; +}; + /// \brief Handle a runtime type check failure, caused by an incorrect vptr. /// When this handler is called, all we know is that the type was not in the /// cache; this does not necessarily imply the existence of a bug. @@ -35,6 +41,13 @@ extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __ubsan_handle_dynamic_type_cache_miss_abort( DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash); +/// \brief Handle a control flow integrity check failure by printing a +/// diagnostic. +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void +__ubsan_handle_cfi_bad_type(CFIBadTypeData *Data, ValueHandle Vtable); +extern "C" SANITIZER_INTERFACE_ATTRIBUTE void +__ubsan_handle_cfi_bad_type_abort(CFIBadTypeData *Data, ValueHandle Vtable); + } #endif // UBSAN_HANDLERS_H diff --git a/compiler-rt/lib/ubsan/ubsan_type_hash.cc b/compiler-rt/lib/ubsan/ubsan_type_hash.cc index 441f713..c130afd 100644 --- a/compiler-rt/lib/ubsan/ubsan_type_hash.cc +++ b/compiler-rt/lib/ubsan/ubsan_type_hash.cc @@ -196,11 +196,11 @@ struct VtablePrefix { /// The type_info object describing the most-derived class type. std::type_info *TypeInfo; }; -VtablePrefix *getVtablePrefix(void *Object) { - VtablePrefix **VptrPtr = reinterpret_cast(Object); - if (!*VptrPtr) +VtablePrefix *getVtablePrefix(void *Vtable) { + VtablePrefix *Vptr = reinterpret_cast(Vtable); + if (!Vptr) return 0; - VtablePrefix *Prefix = *VptrPtr - 1; + VtablePrefix *Prefix = Vptr - 1; if (Prefix->Offset > 0 || !Prefix->TypeInfo) // This can't possibly be a valid vtable. return 0; @@ -220,7 +220,8 @@ bool __ubsan::checkDynamicType(void *Object, void *Type, HashValue Hash) { return true; } - VtablePrefix *Vtable = getVtablePrefix(Object); + void *VtablePtr = *reinterpret_cast(Object); + VtablePrefix *Vtable = getVtablePrefix(VtablePtr); if (!Vtable) return false; @@ -240,8 +241,14 @@ bool __ubsan::checkDynamicType(void *Object, void *Type, HashValue Hash) { return true; } -__ubsan::DynamicTypeInfo __ubsan::getDynamicTypeInfo(void *Object) { - VtablePrefix *Vtable = getVtablePrefix(Object); +__ubsan::DynamicTypeInfo __ubsan::getDynamicTypeInfoFromObject(void *Object) { + void *VtablePtr = *reinterpret_cast(Object); + return getDynamicTypeInfoFromVtable(VtablePtr); +} + +__ubsan::DynamicTypeInfo +__ubsan::getDynamicTypeInfoFromVtable(void *VtablePtr) { + VtablePrefix *Vtable = getVtablePrefix(VtablePtr); if (!Vtable) return DynamicTypeInfo(0, 0, 0); const abi::__class_type_info *ObjectType = findBaseAtOffset( diff --git a/compiler-rt/lib/ubsan/ubsan_type_hash.h b/compiler-rt/lib/ubsan/ubsan_type_hash.h index 58ecd3d..695fed9 100644 --- a/compiler-rt/lib/ubsan/ubsan_type_hash.h +++ b/compiler-rt/lib/ubsan/ubsan_type_hash.h @@ -41,7 +41,10 @@ public: }; /// \brief Get information about the dynamic type of an object. -DynamicTypeInfo getDynamicTypeInfo(void *Object); +DynamicTypeInfo getDynamicTypeInfoFromObject(void *Object); + +/// \brief Get information about the dynamic type of an object from its vtable. +DynamicTypeInfo getDynamicTypeInfoFromVtable(void *Vtable); /// \brief Check whether the dynamic type of \p Object has a \p Type subobject /// at offset 0. diff --git a/compiler-rt/test/cfi/CMakeLists.txt b/compiler-rt/test/cfi/CMakeLists.txt index 625e3ff..a6f1365 100644 --- a/compiler-rt/test/cfi/CMakeLists.txt +++ b/compiler-rt/test/cfi/CMakeLists.txt @@ -9,6 +9,7 @@ if(NOT COMPILER_RT_STANDALONE_BUILD) FileCheck clang not + ubsan ) if(LLVM_ENABLE_PIC AND LLVM_BINUTILS_INCDIR) list(APPEND CFI_TEST_DEPS diff --git a/compiler-rt/test/cfi/anon-namespace.cpp b/compiler-rt/test/cfi/anon-namespace.cpp index 0c2c689..69cc102 100644 --- a/compiler-rt/test/cfi/anon-namespace.cpp +++ b/compiler-rt/test/cfi/anon-namespace.cpp @@ -23,6 +23,11 @@ // RUN: %clangxx -o %t %t1.o %t2.o // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_diag -c -DTU1 -o %t1.o %s +// RUN: %clangxx_cfi_diag -c -DTU2 -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx_cfi_diag -o %t %t1.o %t2.o +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG %s + // Tests that the CFI mechanism treats classes in the anonymous namespace in // different translation units as having distinct identities. This is done by // compiling two translation units TU1 and TU2 containing a class named B in an @@ -83,6 +88,10 @@ int main() { // NCFI: 1 fprintf(stderr, "1\n"); + // CFI-DIAG: runtime error: control flow integrity check for type '(anonymous namespace)::B' failed during base-to-derived cast + // CFI-DIAG-NEXT: note: vtable is of type '(anonymous namespace)::B' + // CFI-DIAG: runtime error: control flow integrity check for type '(anonymous namespace)::B' failed during virtual call + // CFI-DIAG-NEXT: note: vtable is of type '(anonymous namespace)::B' ((B *)a)->f(); // UB here // CFI-NOT: 2 diff --git a/compiler-rt/test/cfi/bad-cast.cpp b/compiler-rt/test/cfi/bad-cast.cpp index 712262d..47098f0 100644 --- a/compiler-rt/test/cfi/bad-cast.cpp +++ b/compiler-rt/test/cfi/bad-cast.cpp @@ -58,6 +58,12 @@ // RUN: %t g 2>&1 | FileCheck --check-prefix=PASS %s // RUN: %t h 2>&1 | FileCheck --check-prefix=PASS %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t a 2>&1 | FileCheck --check-prefix=CFI-DIAG-D %s +// RUN: %t b 2>&1 | FileCheck --check-prefix=CFI-DIAG-D %s +// RUN: %t c 2>&1 | FileCheck --check-prefix=CFI-DIAG-D %s +// RUN: %t g 2>&1 | FileCheck --check-prefix=CFI-DIAG-U %s + // Tests that the CFI enforcement detects bad casts. #include @@ -102,6 +108,13 @@ int main(int argc, char **argv) { fprintf(stderr, "1\n"); A a; + + // CFI-DIAG-D: runtime error: control flow integrity check for type 'B' failed during base-to-derived cast + // CFI-DIAG-D-NEXT: note: vtable is of type 'A' + + // CFI-DIAG-U: runtime error: control flow integrity check for type 'B' failed during cast to unrelated type + // CFI-DIAG-U-NEXT: note: vtable is of type 'A' + switch (argv[1][0]) { case 'a': static_cast(&a); // UB diff --git a/compiler-rt/test/cfi/lit.cfg b/compiler-rt/test/cfi/lit.cfg index 2343ff1..86cc403 100644 --- a/compiler-rt/test/cfi/lit.cfg +++ b/compiler-rt/test/cfi/lit.cfg @@ -9,7 +9,9 @@ clangxx = ' '.join([config.clang] + config.cxx_mode_flags) config.substitutions.append((r"%clangxx ", clangxx + ' ')) if config.lto_supported: - config.substitutions.append((r"%clangxx_cfi ", ' '.join(config.lto_launch + [clangxx] + config.lto_flags + ['-fsanitize=cfi ']))) + clangxx_cfi = ' '.join(config.lto_launch + [clangxx] + config.lto_flags + ['-fsanitize=cfi ']) + config.substitutions.append((r"%clangxx_cfi ", clangxx_cfi)) + config.substitutions.append((r"%clangxx_cfi_diag ", clangxx_cfi + '-fno-sanitize-trap=cfi -fsanitize-recover=cfi ')) else: config.unsupported = True diff --git a/compiler-rt/test/cfi/multiple-inheritance.cpp b/compiler-rt/test/cfi/multiple-inheritance.cpp index 523af6f..93a432a 100644 --- a/compiler-rt/test/cfi/multiple-inheritance.cpp +++ b/compiler-rt/test/cfi/multiple-inheritance.cpp @@ -18,6 +18,10 @@ // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s // RUN: %t x 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG2 %s +// RUN: %t x 2>&1 | FileCheck --check-prefix=CFI-DIAG1 %s + // Tests that the CFI mechanism is sensitive to multiple inheritance and only // permits calls via virtual tables for the correct base class. @@ -70,8 +74,12 @@ int main(int argc, char **argv) { if (argc > 1) { A *a = c; + // CFI-DIAG1: runtime error: control flow integrity check for type 'B' failed during cast to unrelated type + // CFI-DIAG1-NEXT: note: vtable is of type 'C' ((B *)a)->g(); // UB here } else { + // CFI-DIAG2: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type + // CFI-DIAG2-NEXT: note: vtable is of type 'C' B *b = c; ((A *)b)->f(); // UB here } diff --git a/compiler-rt/test/cfi/nvcall.cpp b/compiler-rt/test/cfi/nvcall.cpp index e4385ee..7158294 100644 --- a/compiler-rt/test/cfi/nvcall.cpp +++ b/compiler-rt/test/cfi/nvcall.cpp @@ -13,6 +13,9 @@ // RUN: %clangxx -o %t %s // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG %s + // Tests that the CFI mechanism crashes the program when making a non-virtual // call to an object of the wrong class, by casting a pointer to such an object // and attempting to make a call through it. @@ -21,10 +24,10 @@ #include "utils.h" struct A { - virtual void f(); + virtual void v(); }; -void A::f() {} +void A::v() {} struct B { void f(); @@ -57,6 +60,8 @@ int main() { // NCFI: 1 fprintf(stderr, "1\n"); + // CFI-DIAG: runtime error: control flow integrity check for type 'B' failed during non-virtual call + // CFI-DIAG-NEXT: note: vtable is of type 'A' ((B *)a)->f(); // UB here // CFI-NOT: 2 diff --git a/compiler-rt/test/cfi/overwrite.cpp b/compiler-rt/test/cfi/overwrite.cpp index d7e58d9..d983cf7 100644 --- a/compiler-rt/test/cfi/overwrite.cpp +++ b/compiler-rt/test/cfi/overwrite.cpp @@ -13,6 +13,9 @@ // RUN: %clangxx -o %t %s // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG %s + // Tests that the CFI mechanism crashes the program when a virtual table is // replaced with a compatible table of function pointers that does not belong to // any class, by manually overwriting the virtual table of an object and @@ -31,7 +34,7 @@ void foo() { fprintf(stderr, "foo\n"); } -void *fake_vtable[] = { (void *)&foo }; +void *fake_vtable[] = { 0, 0, (void *)&foo }; int main() { #ifdef B32 @@ -50,7 +53,7 @@ int main() { #endif A *a = new A; - *((void **)a) = fake_vtable; // UB here + *((void **)a) = fake_vtable + 2; // UB here break_optimization(a); // CFI: 1 @@ -59,6 +62,8 @@ int main() { // CFI-NOT: foo // NCFI: foo + // CFI-DIAG: runtime error: control flow integrity check for type 'A' failed during virtual call + // CFI-DIAG-NEXT: note: invalid vtable a->f(); // CFI-NOT: 2 diff --git a/compiler-rt/test/cfi/simple-fail.cpp b/compiler-rt/test/cfi/simple-fail.cpp index cf24f86..2be560b 100644 --- a/compiler-rt/test/cfi/simple-fail.cpp +++ b/compiler-rt/test/cfi/simple-fail.cpp @@ -46,6 +46,9 @@ // RUN: %clangxx_cfi -O3 -DBM -o %t %s // RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG %s + // RUN: %clangxx -o %t %s // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s @@ -91,6 +94,10 @@ int main() { // NCFI: 1 fprintf(stderr, "1\n"); + // CFI-DIAG: runtime error: control flow integrity check for type 'B' failed during cast to unrelated type + // CFI-DIAG-NEXT: note: vtable is of type 'A' + // CFI-DIAG: runtime error: control flow integrity check for type 'B' failed during virtual call + // CFI-DIAG-NEXT: note: vtable is of type 'A' ((B *)a)->f(); // UB here // CFI-NOT: 2 diff --git a/compiler-rt/test/cfi/vdtor.cpp b/compiler-rt/test/cfi/vdtor.cpp index e21883c..f42a2d9 100644 --- a/compiler-rt/test/cfi/vdtor.cpp +++ b/compiler-rt/test/cfi/vdtor.cpp @@ -13,6 +13,9 @@ // RUN: %clangxx -o %t %s // RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_diag -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CFI-DIAG %s + // Tests that the CFI enforcement also applies to virtual destructor calls made // via 'delete'. @@ -54,6 +57,8 @@ int main() { // NCFI: 1 fprintf(stderr, "1\n"); + // CFI-DIAG: runtime error: control flow integrity check for type 'B' failed during virtual call + // CFI-DIAG-NEXT: note: vtable is of type 'A' delete (B *)a; // UB here // CFI-NOT: 2 -- 2.7.4