diff options
Diffstat (limited to 'test/cfi')
-rw-r--r-- | test/cfi/CMakeLists.txt | 20 | ||||
-rw-r--r-- | test/cfi/create-derivers.test | 8 | ||||
-rw-r--r-- | test/cfi/cross-dso/dlopen.cpp | 147 | ||||
-rw-r--r-- | test/cfi/cross-dso/icall/diag.cpp | 159 | ||||
-rw-r--r-- | test/cfi/cross-dso/icall/icall-from-dso.cpp | 8 | ||||
-rw-r--r-- | test/cfi/cross-dso/icall/icall.cpp | 8 | ||||
-rw-r--r-- | test/cfi/cross-dso/shadow_is_read_only.cpp | 85 | ||||
-rw-r--r-- | test/cfi/cross-dso/simple-fail.cpp | 9 | ||||
-rw-r--r-- | test/cfi/cross-dso/stats.cpp | 59 | ||||
-rw-r--r-- | test/cfi/cross-dso/target_out_of_bounds.cpp | 64 | ||||
-rw-r--r-- | test/cfi/icall/bad-signature.c | 8 | ||||
-rw-r--r-- | test/cfi/icall/external-call.c | 2 | ||||
-rw-r--r-- | test/cfi/lit.cfg | 26 | ||||
-rw-r--r-- | test/cfi/lit.site.cfg.in | 4 | ||||
-rw-r--r-- | test/cfi/overwrite.cpp | 7 | ||||
-rw-r--r-- | test/cfi/stats.cpp | 52 | ||||
-rw-r--r-- | test/cfi/target_uninstrumented.cpp | 44 |
17 files changed, 689 insertions, 21 deletions
diff --git a/test/cfi/CMakeLists.txt b/test/cfi/CMakeLists.txt index 5626a6e50ed7..4c4debaf1a87 100644 --- a/test/cfi/CMakeLists.txt +++ b/test/cfi/CMakeLists.txt @@ -1,6 +1,13 @@ +set(CFI_LIT_TEST_MODE Standalone) configure_lit_site_cfg( ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in - ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ${CMAKE_CURRENT_BINARY_DIR}/Standalone/lit.site.cfg + ) + +set(CFI_LIT_TEST_MODE Devirt) +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/Devirt/lit.site.cfg ) set(CFI_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) @@ -8,6 +15,8 @@ if(NOT COMPILER_RT_STANDALONE_BUILD) list(APPEND CFI_TEST_DEPS opt ubsan + stats + sanstats ) if(COMPILER_RT_HAS_CFI) list(APPEND CFI_TEST_DEPS cfi) @@ -30,12 +39,15 @@ if(NOT COMPILER_RT_STANDALONE_BUILD) endif() add_lit_testsuite(check-cfi "Running the cfi regression tests" - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/Standalone + ${CMAKE_CURRENT_BINARY_DIR}/Devirt DEPENDS ${CFI_TEST_DEPS}) add_lit_target(check-cfi-and-supported "Running the cfi regression tests" - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/Standalone + ${CMAKE_CURRENT_BINARY_DIR}/Devirt PARAMS check_supported=1 DEPENDS ${CFI_TEST_DEPS}) -set_target_properties(check-cfi PROPERTIES FOLDER "Tests") +set_target_properties(check-cfi PROPERTIES FOLDER "Compiler-RT Misc") +set_target_properties(check-cfi-and-supported PROPERTIES FOLDER "Compiler-RT Misc") diff --git a/test/cfi/create-derivers.test b/test/cfi/create-derivers.test index 79521e4d085a..a67562b1a6c8 100644 --- a/test/cfi/create-derivers.test +++ b/test/cfi/create-derivers.test @@ -1,20 +1,20 @@ REQUIRES: asserts RUN: %clangxx_cfi -c -o %t1.o %S/simple-fail.cpp -RUN: opt -lowerbitsets -debug-only=lowerbitsets -o /dev/null %t1.o 2>&1 | FileCheck --check-prefix=B0 %s +RUN: opt -lowertypetests -debug-only=lowertypetests -o /dev/null %t1.o 2>&1 | FileCheck --check-prefix=B0 %s B0: {{1B|B@@}}: {{.*}} size 1 RUN: %clangxx_cfi -DB32 -c -o %t2.o %S/simple-fail.cpp -RUN: opt -lowerbitsets -debug-only=lowerbitsets -o /dev/null %t2.o 2>&1 | FileCheck --check-prefix=B32 %s +RUN: opt -lowertypetests -debug-only=lowertypetests -o /dev/null %t2.o 2>&1 | FileCheck --check-prefix=B32 %s B32: {{1B|B@@}}: {{.*}} size 24 B32-NOT: all-ones RUN: %clangxx_cfi -DB64 -c -o %t3.o %S/simple-fail.cpp -RUN: opt -lowerbitsets -debug-only=lowerbitsets -o /dev/null %t3.o 2>&1 | FileCheck --check-prefix=B64 %s +RUN: opt -lowertypetests -debug-only=lowertypetests -o /dev/null %t3.o 2>&1 | FileCheck --check-prefix=B64 %s B64: {{1B|B@@}}: {{.*}} size 54 B64-NOT: all-ones RUN: %clangxx_cfi -DBM -c -o %t4.o %S/simple-fail.cpp -RUN: opt -lowerbitsets -debug-only=lowerbitsets -o /dev/null %t4.o 2>&1 | FileCheck --check-prefix=BM %s +RUN: opt -lowertypetests -debug-only=lowertypetests -o /dev/null %t4.o 2>&1 | FileCheck --check-prefix=BM %s BM: {{1B|B@@}}: {{.*}} size 84 BM-NOT: all-ones diff --git a/test/cfi/cross-dso/dlopen.cpp b/test/cfi/cross-dso/dlopen.cpp new file mode 100644 index 000000000000..ee4dae2b5f7d --- /dev/null +++ b/test/cfi/cross-dso/dlopen.cpp @@ -0,0 +1,147 @@ +// RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t1-so.so +// RUN: %clangxx_cfi_dso %s -o %t1 +// RUN: %expect_crash %t1 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %expect_crash %t1 cast 2>&1 | FileCheck --check-prefix=CFI-CAST %s +// RUN: %expect_crash %t1 dlclose 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi_dso -DB32 -DSHARED_LIB %s -fPIC -shared -o %t2-so.so +// RUN: %clangxx_cfi_dso -DB32 %s -o %t2 +// RUN: %expect_crash %t2 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %expect_crash %t2 cast 2>&1 | FileCheck --check-prefix=CFI-CAST %s +// RUN: %expect_crash %t2 dlclose 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi_dso -DB64 -DSHARED_LIB %s -fPIC -shared -o %t3-so.so +// RUN: %clangxx_cfi_dso -DB64 %s -o %t3 +// RUN: %expect_crash %t3 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %expect_crash %t3 cast 2>&1 | FileCheck --check-prefix=CFI-CAST %s +// RUN: %expect_crash %t3 dlclose 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi_dso -DBM -DSHARED_LIB %s -fPIC -shared -o %t4-so.so +// RUN: %clangxx_cfi_dso -DBM %s -o %t4 +// RUN: %expect_crash %t4 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %expect_crash %t4 cast 2>&1 | FileCheck --check-prefix=CFI-CAST %s +// RUN: %expect_crash %t4 dlclose 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -g -DBM -DSHARED_LIB -DNOCFI %s -fPIC -shared -o %t5-so.so +// RUN: %clangxx -g -DBM -DNOCFI %s -ldl -o %t5 +// RUN: %t5 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %t5 cast 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %t5 dlclose 2>&1 | FileCheck --check-prefix=NCFI %s + +// Test that calls to uninstrumented library are unchecked. +// RUN: %clangxx -DBM -DSHARED_LIB %s -fPIC -shared -o %t6-so.so +// RUN: %clangxx_cfi_dso -DBM %s -o %t6 +// RUN: %t6 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %t6 cast 2>&1 | FileCheck --check-prefix=NCFI %s + +// Call-after-dlclose is checked on the caller side. +// RUN: %expect_crash %t6 dlclose 2>&1 | FileCheck --check-prefix=CFI %s + +// Tests calls into dlopen-ed library. +// REQUIRES: cxxabi + +#include <assert.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <sys/mman.h> + +#include <string> + +struct A { + virtual void f(); +}; + +#ifdef SHARED_LIB + +#include "../utils.h" +struct B { + virtual void f(); +}; +void B::f() {} + +extern "C" void *create_B() { + create_derivers<B>(); + return (void *)(new B()); +} + +extern "C" __attribute__((aligned(4096))) void do_nothing() {} + +#else + +void A::f() {} + +static const int kCodeAlign = 4096; +static const int kCodeSize = 4096; +static char saved_code[kCodeSize]; +static char *real_start; + +static void save_code(char *p) { + real_start = (char *)(((uintptr_t)p) & ~(kCodeAlign - 1)); + memcpy(saved_code, real_start, kCodeSize); +} + +static void restore_code() { + char *code = (char *)mmap(real_start, kCodeSize, PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0); + assert(code == real_start); + memcpy(code, saved_code, kCodeSize); +} + +int main(int argc, char *argv[]) { + const bool test_cast = argc > 1 && strcmp(argv[1], "cast") == 0; + const bool test_dlclose = argc > 1 && strcmp(argv[1], "dlclose") == 0; + + std::string name = std::string(argv[0]) + "-so.so"; + void *handle = dlopen(name.c_str(), RTLD_NOW); + assert(handle); + void *(*create_B)() = (void *(*)())dlsym(handle, "create_B"); + assert(create_B); + + void *p = create_B(); + A *a; + + // CFI: =0= + // CFI-CAST: =0= + // NCFI: =0= + fprintf(stderr, "=0=\n"); + + if (test_cast) { + // Test cast. BOOM. + a = (A*)p; + } else { + // Invisible to CFI. Test virtual call later. + memcpy(&a, &p, sizeof(a)); + } + + // CFI: =1= + // CFI-CAST-NOT: =1= + // NCFI: =1= + fprintf(stderr, "=1=\n"); + + if (test_dlclose) { + // Imitate an attacker sneaking in an executable page where a dlclose()d + // library was loaded. This needs to pass w/o CFI, so for the testing + // purpose, we just copy the bytes of a "void f() {}" function back and + // forth. + void (*do_nothing)() = (void (*)())dlsym(handle, "do_nothing"); + assert(do_nothing); + save_code((char *)do_nothing); + + int res = dlclose(handle); + assert(res == 0); + + restore_code(); + + do_nothing(); // UB here + } else { + a->f(); // UB here + } + + // CFI-NOT: =2= + // CFI-CAST-NOT: =2= + // NCFI: =2= + fprintf(stderr, "=2=\n"); +} +#endif diff --git a/test/cfi/cross-dso/icall/diag.cpp b/test/cfi/cross-dso/icall/diag.cpp new file mode 100644 index 000000000000..c9ca28cbf2cd --- /dev/null +++ b/test/cfi/cross-dso/icall/diag.cpp @@ -0,0 +1,159 @@ +// Cross-DSO diagnostics. +// The rules are: +// * If the library needs diagnostics, the main executable must request at +// least some diagnostics as well (to link the diagnostic runtime). +// * -fsanitize-trap on the caller side overrides everything. +// * otherwise, the callee decides between trap/recover/norecover. + +// Full-recover. +// RUN: %clangxx_cfi_dso_diag -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so +// RUN: %clangxx_cfi_dso_diag -g %s -o %t %t-so.so + +// RUN: %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-DIAG \ +// RUN: --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER + +// RUN: %t i_v 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER + +// RUN: %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \ +// RUN: --check-prefix=VCALL-DIAG --check-prefix=ALL-RECOVER + +// RUN: %t ic_ 2>&1 | FileCheck %s --check-prefix=ICALL-DIAG --check-prefix=CAST-DIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=ALL-RECOVER + +// Trap on icall, no-recover on cast. +// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \ +// RUN: -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so +// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \ +// RUN: -g %s -o %t %t-so.so + +// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL + +// RUN: not %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL + +// RUN: %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-DIAG + +// Callee: trap on icall, no-recover on cast. +// Caller: recover on everything. +// The same as in the previous case, behaviour is decided by the callee. +// RUN: %clangxx_cfi_dso_diag -fsanitize-trap=cfi-icall -fno-sanitize-recover=cfi-unrelated-cast \ +// RUN: -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so +// RUN: %clangxx_cfi_dso_diag \ +// RUN: -g %s -o %t %t-so.so + +// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL + +// RUN: not %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-DIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL + +// RUN: %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-DIAG + +// Caller in trapping mode, callee with full diagnostic+recover. +// Caller wins. +// cfi-nvcall is non-trapping in the main executable to link the diagnostic runtime library. +// RUN: %clangxx_cfi_dso_diag \ +// RUN: -g -DSHARED_LIB %s -fPIC -shared -o %t-so.so +// RUN: %clangxx_cfi_dso -fno-sanitize-trap=cfi-nvcall \ +// RUN: -g %s -o %t %t-so.so + +// RUN: %expect_crash %t icv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=ICALL-FATAL + +// RUN: %expect_crash %t _cv 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=CAST-FATAL + +// RUN: %expect_crash %t __v 2>&1 | FileCheck %s --check-prefix=ICALL-NODIAG --check-prefix=CAST-NODIAG \ +// RUN: --check-prefix=VCALL-NODIAG --check-prefix=VCALL-FATAL + +// REQUIRES: cxxabi + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +struct A { + virtual void f(); +}; + +void *create_B(); + +#ifdef SHARED_LIB + +#include "../../utils.h" +struct B { + virtual void f(); +}; +void B::f() {} + +void *create_B() { + create_derivers<B>(); + return (void *)(new B()); +} + +#else + +void A::f() {} + +int main(int argc, char *argv[]) { + assert(argc == 2); + assert(strlen(argv[1]) == 3); + + // ICALL-FATAL: =0= + // CAST-FATAL: =0= + // VCALL-FATAL: =0= + // ALL-RECOVER: =0= + fprintf(stderr, "=0=\n"); + + void *p; + if (argv[1][0] == 'i') { + // ICALL-DIAG: runtime error: control flow integrity check for type 'void *(int)' failed during indirect function call + // ICALL-DIAG-NEXT: note: create_B() defined here + // ICALL-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during indirect function call + p = ((void *(*)(int))create_B)(42); + } else { + p = create_B(); + } + + // ICALL-FATAL-NOT: =1= + // CAST-FATAL: =1= + // VCALL-FATAL: =1= + // ALL-RECOVER: =1= + fprintf(stderr, "=1=\n"); + + A *a; + if (argv[1][1] == 'c') { + // CAST-DIAG: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type + // CAST-DIAG-NEXT: note: vtable is of type '{{(struct )?}}B' + // CAST-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during cast to unrelated type + a = (A*)p; + } else { + // Invisible to CFI. + memcpy(&a, &p, sizeof(a)); + } + + // ICALL-FATAL-NOT: =2= + // CAST-FATAL-NOT: =2= + // VCALL-FATAL: =2= + // ALL-RECOVER: =2= + fprintf(stderr, "=2=\n"); + + // VCALL-DIAG: runtime error: control flow integrity check for type 'A' failed during virtual call + // VCALL-DIAG-NEXT: note: vtable is of type '{{(struct )?}}B' + // VCALL-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during virtual call + if (argv[1][2] == 'v') { + a->f(); // UB here + } + + // ICALL-FATAL-NOT: =3= + // CAST-FATAL-NOT: =3= + // VCALL-FATAL-NOT: =3= + // ALL-RECOVER: =3= + fprintf(stderr, "=3=\n"); + +} +#endif diff --git a/test/cfi/cross-dso/icall/icall-from-dso.cpp b/test/cfi/cross-dso/icall/icall-from-dso.cpp index 1995f05f43d4..93cf4f676f7b 100644 --- a/test/cfi/cross-dso/icall/icall-from-dso.cpp +++ b/test/cfi/cross-dso/icall/icall-from-dso.cpp @@ -1,17 +1,25 @@ // RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t-so.so // RUN: %clangxx_cfi_dso %s -o %t %t-so.so && %expect_crash %t 2>&1 | FileCheck %s +// RUN: %clangxx_cfi_dso_diag -g -DSHARED_LIB %s -fPIC -shared -o %t2-so.so +// RUN: %clangxx_cfi_dso_diag -g %s -o %t2 %t2-so.so && %t2 2>&1 | FileCheck %s --check-prefix=CFI-DIAG + #include <stdio.h> #ifdef SHARED_LIB void g(); void f() { + // CHECK-DIAG: =1= // CHECK: =1= fprintf(stderr, "=1=\n"); ((void (*)(void))g)(); + // CHECK-DIAG: =2= // CHECK: =2= fprintf(stderr, "=2=\n"); + // CFI-DIAG: runtime error: control flow integrity check for type 'void (int)' failed during indirect function call + // CFI-DIAG-NEXT: note: g() defined here ((void (*)(int))g)(42); // UB here + // CHECK-DIAG: =3= // CHECK-NOT: =3= fprintf(stderr, "=3=\n"); } diff --git a/test/cfi/cross-dso/icall/icall.cpp b/test/cfi/cross-dso/icall/icall.cpp index d7cc2f9ca94d..6017b801436e 100644 --- a/test/cfi/cross-dso/icall/icall.cpp +++ b/test/cfi/cross-dso/icall/icall.cpp @@ -1,6 +1,9 @@ // RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t-so.so // RUN: %clangxx_cfi_dso %s -o %t %t-so.so && %expect_crash %t 2>&1 | FileCheck %s +// RUN: %clangxx_cfi_dso_diag -g -DSHARED_LIB %s -fPIC -shared -o %t2-so.so +// RUN: %clangxx_cfi_dso_diag -g %s -o %t2 %t2-so.so && %t2 2>&1 | FileCheck %s --check-prefix=CFI-DIAG + #include <stdio.h> #ifdef SHARED_LIB @@ -9,12 +12,17 @@ void f() { #else void f(); int main() { + // CHECK-DIAG: =1= // CHECK: =1= fprintf(stderr, "=1=\n"); ((void (*)(void))f)(); + // CHECK-DIAG: =2= // CHECK: =2= fprintf(stderr, "=2=\n"); + // CFI-DIAG: runtime error: control flow integrity check for type 'void (int)' failed during indirect function call + // CFI-DIAG-NEXT: note: f() defined here ((void (*)(int))f)(42); // UB here + // CHECK-DIAG: =3= // CHECK-NOT: =3= fprintf(stderr, "=3=\n"); } diff --git a/test/cfi/cross-dso/shadow_is_read_only.cpp b/test/cfi/cross-dso/shadow_is_read_only.cpp new file mode 100644 index 000000000000..65aec826c001 --- /dev/null +++ b/test/cfi/cross-dso/shadow_is_read_only.cpp @@ -0,0 +1,85 @@ +// RUN: %clangxx_cfi_dso -std=c++11 -g -DSHARED_LIB %s -fPIC -shared -o %t-cfi-so.so +// RUN: %clangxx -std=c++11 -g -DSHARED_LIB %s -fPIC -shared -o %t-nocfi-so.so +// RUN: %clangxx_cfi_dso -std=c++11 -g %s -o %t + +// RUN: %expect_crash %t start 2>&1 | FileCheck %s +// RUN: %expect_crash %t mmap 2>&1 | FileCheck %s +// RUN: %expect_crash %t dlopen %t-cfi-so.so 2>&1 | FileCheck %s +// RUN: %expect_crash %t dlclose %t-cfi-so.so 2>&1 | FileCheck %s +// RUN: %expect_crash %t dlopen %t-nocfi-so.so 2>&1 | FileCheck %s +// RUN: %expect_crash %t dlclose %t-nocfi-so.so 2>&1 | FileCheck %s + +// Tests that shadow is read-only most of the time. +// REQUIRES: cxxabi + +#include <assert.h> +#include <dlfcn.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> + +struct A { + virtual void f(); +}; + +#ifdef SHARED_LIB + +void A::f() {} + +extern "C" A *create_A() { return new A(); } + +#else + +constexpr unsigned kShadowGranularity = 12; + +namespace __cfi { +uintptr_t GetShadow(); +} + +void write_shadow(void *ptr) { + uintptr_t base = __cfi::GetShadow(); + uint16_t *s = + (uint16_t *)(base + (((uintptr_t)ptr >> kShadowGranularity) << 1)); + fprintf(stderr, "going to crash\n"); + // CHECK: going to crash + *s = 42; + fprintf(stderr, "did not crash\n"); + // CHECK-NOT: did not crash + exit(1); +} + +int main(int argc, char *argv[]) { + assert(argc > 1); + const bool test_mmap = strcmp(argv[1], "mmap") == 0; + const bool test_start = strcmp(argv[1], "start") == 0; + const bool test_dlopen = strcmp(argv[1], "dlopen") == 0; + const bool test_dlclose = strcmp(argv[1], "dlclose") == 0; + const char *lib = argc > 2 ? argv[2] : nullptr; + + if (test_start) + write_shadow((void *)&main); + + if (test_mmap) { + void *p = mmap(nullptr, 1 << 20, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + assert(p != MAP_FAILED); + write_shadow((char *)p + 100); + } else { + void *handle = dlopen(lib, RTLD_NOW); + assert(handle); + void *create_A = dlsym(handle, "create_A"); + assert(create_A); + + if (test_dlopen) + write_shadow(create_A); + + int res = dlclose(handle); + assert(res == 0); + + if (test_dlclose) + write_shadow(create_A); + } +} +#endif diff --git a/test/cfi/cross-dso/simple-fail.cpp b/test/cfi/cross-dso/simple-fail.cpp index 64db288a95b5..276b67d4b7f2 100644 --- a/test/cfi/cross-dso/simple-fail.cpp +++ b/test/cfi/cross-dso/simple-fail.cpp @@ -28,6 +28,11 @@ // RUN: %t6 2>&1 | FileCheck --check-prefix=NCFI %s // RUN: %t6 x 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %clangxx_cfi_dso_diag -DSHARED_LIB %s -fPIC -shared -o %t7-so.so +// RUN: %clangxx_cfi_dso_diag %s -o %t7 %t7-so.so +// RUN: %t7 2>&1 | FileCheck --check-prefix=CFI-DIAG-CALL %s +// RUN: %t7 x 2>&1 | FileCheck --check-prefix=CFI-DIAG-CALL --check-prefix=CFI-DIAG-CAST %s + // Tests that the CFI mechanism crashes the program when making a virtual call // to an object of the wrong class but with a compatible vtable, by casting a // pointer to such an object and attempting to make a call through it. @@ -71,6 +76,8 @@ int main(int argc, char *argv[]) { if (argc > 1 && argv[1][0] == 'x') { // Test cast. BOOM. + // CFI-DIAG-CAST: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type + // CFI-DIAG-CAST-NEXT: note: vtable is of type '{{(struct )?}}B' a = (A*)p; } else { // Invisible to CFI. Test virtual call later. @@ -82,6 +89,8 @@ int main(int argc, char *argv[]) { // NCFI: =1= fprintf(stderr, "=1=\n"); + // CFI-DIAG-CALL: runtime error: control flow integrity check for type 'A' failed during virtual call + // CFI-DIAG-CALL-NEXT: note: vtable is of type '{{(struct )?}}B' a->f(); // UB here // CFI-NOT: =2= diff --git a/test/cfi/cross-dso/stats.cpp b/test/cfi/cross-dso/stats.cpp new file mode 100644 index 000000000000..3d25d7730c77 --- /dev/null +++ b/test/cfi/cross-dso/stats.cpp @@ -0,0 +1,59 @@ +// RUN: %clangxx_cfi_dso -DSHARED_LIB -fPIC -g -fsanitize-stats -shared -o %t.so %s +// RUN: %clangxx_cfi_dso -g -fsanitize-stats -o %t %s %t.so +// RUN: env SANITIZER_STATS_PATH=%t.stats %t +// RUN: sanstats %t.stats | FileCheck %s + +struct ABase {}; + +struct A : ABase { + virtual void vf() {} + void nvf() {} +}; + +extern "C" void vcall(A *a); +extern "C" void nvcall(A *a); + +#ifdef SHARED_LIB + +extern "C" __attribute__((noinline)) void vcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] vcall cfi-vcall 37 + a->vf(); +} + +extern "C" __attribute__((noinline)) void nvcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] nvcall cfi-nvcall 51 + a->nvf(); +} + +#else + +extern "C" __attribute__((noinline)) A *dcast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] dcast cfi-derived-cast 24 + return (A *)(ABase *)a; +} + +extern "C" __attribute__((noinline)) A *ucast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] ucast cfi-unrelated-cast 81 + return (A *)(char *)a; +} + +extern "C" __attribute__((noinline)) void unreachable(A *a) { + // CHECK-NOT: unreachable + a->vf(); +} + +int main() { + A a; + for (unsigned i = 0; i != 37; ++i) + vcall(&a); + for (unsigned i = 0; i != 51; ++i) + nvcall(&a); + for (unsigned i = 0; i != 24; ++i) + dcast(&a); + for (unsigned i = 0; i != 81; ++i) + ucast(&a); + for (unsigned i = 0; i != 0; ++i) + unreachable(&a); +} + +#endif diff --git a/test/cfi/cross-dso/target_out_of_bounds.cpp b/test/cfi/cross-dso/target_out_of_bounds.cpp new file mode 100644 index 000000000000..6353f030a6ac --- /dev/null +++ b/test/cfi/cross-dso/target_out_of_bounds.cpp @@ -0,0 +1,64 @@ +// RUN: %clangxx_cfi_dso_diag -std=c++11 %s -o %t +// RUN: %t zero 2>&1 | FileCheck --check-prefix=CHECK-ZERO %s +// RUN: %t unaddressable 2>&1 | FileCheck --check-prefix=CHECK-UNADDR %s +// RUN: %t 2>&1 | FileCheck --check-prefix=CHECK-TYPEINFO %s + +// RUN: %clangxx_cfi_diag -std=c++11 %s -o %t2 +// RUN: %t2 zero 2>&1 | FileCheck --check-prefix=CHECK-ZERO %s +// RUN: %t2 unaddressable 2>&1 | FileCheck --check-prefix=CHECK-UNADDR %s +// RUN: %t2 2>&1 | FileCheck --check-prefix=CHECK-TYPEINFO %s + +// REQUIRES: cxxabi + +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> + +struct A { + virtual void f(); +}; + +void A::f() {} + +int main(int argc, char *argv[]) { + char *volatile p = reinterpret_cast<char *>(new A()); + if (argc > 1 && strcmp(argv[1], "unaddressable") == 0) { + void *vtable = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + // Create an object with a vtable in an unaddressable memory region. + *(uintptr_t *)p = (uintptr_t)vtable + 64; + // CHECK-UNADDR: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-UNADDR: note: invalid vtable + // CHECK-UNADDR: <memory cannot be printed> + // CHECK-UNADDR: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-UNADDR: note: invalid vtable + // CHECK-UNADDR: <memory cannot be printed> + } else if (argc > 1 && strcmp(argv[1], "zero") == 0) { + // Create an object with a vtable outside of any known DSO, but still in an + // addressable area. + void *vtable = calloc(1, 128); + *(uintptr_t *)p = (uintptr_t)vtable + 64; + // CHECK-ZERO: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-ZERO: note: invalid vtable + // CHECK-ZERO: 00 00 00 00 00 00 00 00 + // CHECK-ZERO: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-ZERO: note: invalid vtable + // CHECK-ZERO: 00 00 00 00 00 00 00 00 + } else { + // Create an object with a seemingly fine vtable, but with an unaddressable + // typeinfo pointer. + void *vtable = calloc(1, 128); + memset(vtable, 0xFE, 128); + *(uintptr_t *)p = (uintptr_t)vtable + 64; + // CHECK-TYPEINFO: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-TYPEINFO: note: invalid vtable + // CHECK-TYPEINFO: fe fe fe fe fe fe fe fe + // CHECK-TYPEINFO: runtime error: control flow integrity check for type 'A' failed during cast + // CHECK-TYPEINFO: note: invalid vtable + // CHECK-TYPEINFO: fe fe fe fe fe fe fe fe + } + + A *volatile pa = reinterpret_cast<A *>(p); + pa = reinterpret_cast<A *>(p); +} diff --git a/test/cfi/icall/bad-signature.c b/test/cfi/icall/bad-signature.c index 43de1178fe6c..183e62738bb2 100644 --- a/test/cfi/icall/bad-signature.c +++ b/test/cfi/icall/bad-signature.c @@ -1,10 +1,10 @@ -// RUN: %clangxx -o %t1 %s +// RUN: %clang -o %t1 %s // RUN: %t1 2>&1 | FileCheck --check-prefix=NCFI %s -// RUN: %clangxx_cfi -o %t2 %s +// RUN: %clang_cfi -o %t2 %s // RUN: %expect_crash %t2 2>&1 | FileCheck --check-prefix=CFI %s -// RUN: %clangxx_cfi_diag -g -o %t3 %s +// RUN: %clang_cfi_diag -g -o %t3 %s // RUN: %t3 2>&1 | FileCheck --check-prefix=CFI-DIAG %s #include <stdio.h> @@ -18,7 +18,7 @@ int main() { fprintf(stderr, "1\n"); // CFI-DIAG: runtime error: control flow integrity check for type 'void (int)' failed during indirect function call - // CFI-DIAG: f() defined here + // CFI-DIAG: f defined here ((void (*)(int))f)(42); // UB here // CFI-NOT: 2 diff --git a/test/cfi/icall/external-call.c b/test/cfi/icall/external-call.c index 43fc25207562..e90c7e042c27 100644 --- a/test/cfi/icall/external-call.c +++ b/test/cfi/icall/external-call.c @@ -1,4 +1,4 @@ -// RUN: %clangxx_cfi -o %t1 %s +// RUN: %clang_cfi -lm -o %t1 %s // RUN: %t1 c 1 2>&1 | FileCheck --check-prefix=CFI %s // RUN: %t1 s 2 2>&1 | FileCheck --check-prefix=CFI %s diff --git a/test/cfi/lit.cfg b/test/cfi/lit.cfg index 687c80f4f08d..3c0250632f5b 100644 --- a/test/cfi/lit.cfg +++ b/test/cfi/lit.cfg @@ -7,14 +7,28 @@ config.test_source_root = os.path.dirname(__file__) clangxx = ' '.join([config.clang] + config.cxx_mode_flags) +config.substitutions.append((r"%clang ", ' '.join([config.clang]) + ' ')) config.substitutions.append((r"%clangxx ", clangxx + ' ')) if config.lto_supported: - clangxx_cfi = ' '.join(config.lto_launch + [clangxx] + config.lto_flags + ['-flto -fsanitize=cfi ']) - clangxx_cfi_diag = clangxx_cfi + '-fno-sanitize-trap=cfi -fsanitize-recover=cfi ' - config.substitutions.append((r"%clangxx_cfi ", clangxx_cfi)) - config.substitutions.append((r"%clangxx_cfi_diag ", clangxx_cfi_diag)) - config.substitutions.append((r"%clangxx_cfi_dso ", clangxx_cfi + '-fsanitize-cfi-cross-dso ')) - config.substitutions.append((r"%clangxx_cfi_dso_diag ", clangxx_cfi_diag + '-fsanitize-cfi-cross-dso ')) + clang_cfi = ' '.join(config.lto_launch + [config.clang] + config.lto_flags + ['-flto -fsanitize=cfi ']) + + if config.cfi_lit_test_mode == "Devirt": + config.available_features.add('devirt') + clang_cfi += '-fwhole-program-vtables ' + config.substitutions.append((r"%expect_crash_unless_devirt ", "")) + else: + config.substitutions.append((r"%expect_crash_unless_devirt ", config.expect_crash)) + + cxx = ' '.join(config.cxx_mode_flags) + ' ' + diag = '-fno-sanitize-trap=cfi -fsanitize-recover=cfi ' + non_dso = '-fvisibility=hidden ' + dso = '-fsanitize-cfi-cross-dso -fvisibility=default ' + config.substitutions.append((r"%clang_cfi ", clang_cfi + non_dso)) + config.substitutions.append((r"%clangxx_cfi ", clang_cfi + cxx + non_dso)) + config.substitutions.append((r"%clang_cfi_diag ", clang_cfi + non_dso + diag)) + config.substitutions.append((r"%clangxx_cfi_diag ", clang_cfi + cxx + non_dso + diag)) + config.substitutions.append((r"%clangxx_cfi_dso ", clang_cfi + cxx + dso)) + config.substitutions.append((r"%clangxx_cfi_dso_diag ", clang_cfi + cxx + dso + diag)) else: config.unsupported = True diff --git a/test/cfi/lit.site.cfg.in b/test/cfi/lit.site.cfg.in index 76897e701874..87e5b51e7c02 100644 --- a/test/cfi/lit.site.cfg.in +++ b/test/cfi/lit.site.cfg.in @@ -1,2 +1,6 @@ +@LIT_SITE_CFG_IN_HEADER@ + +config.cfi_lit_test_mode = "@CFI_LIT_TEST_MODE@" + lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/test/cfi/overwrite.cpp b/test/cfi/overwrite.cpp index 90f995d87bca..48c0a89c8f66 100644 --- a/test/cfi/overwrite.cpp +++ b/test/cfi/overwrite.cpp @@ -1,5 +1,5 @@ // RUN: %clangxx_cfi -o %t1 %s -// RUN: %expect_crash %t1 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: %expect_crash_unless_devirt %t1 2>&1 | FileCheck --check-prefix=CFI %s // RUN: %clangxx_cfi -DB32 -o %t2 %s // RUN: %expect_crash %t2 2>&1 | FileCheck --check-prefix=CFI %s @@ -55,7 +55,10 @@ int main() { // CFI-DIAG-NEXT: note: invalid vtable a->f(); - // CFI-NOT: {{^2$}} + // We don't check for the absence of a 2 here because under devirtualization + // our virtual call may be devirtualized and we will proceed with execution + // rather than crashing. + // NCFI: {{^2$}} fprintf(stderr, "2\n"); } diff --git a/test/cfi/stats.cpp b/test/cfi/stats.cpp new file mode 100644 index 000000000000..566fcfbc2581 --- /dev/null +++ b/test/cfi/stats.cpp @@ -0,0 +1,52 @@ +// RUN: %clangxx_cfi -g -fsanitize-stats -o %t %s +// RUN: env SANITIZER_STATS_PATH=%t.stats %t +// RUN: sanstats %t.stats | FileCheck %s + +// FIXME: We currently emit the wrong debug info under devirtualization. +// UNSUPPORTED: devirt + +struct ABase {}; + +struct A : ABase { + virtual void vf() {} + void nvf() {} +}; + +extern "C" __attribute__((noinline)) void vcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] {{_?}}vcall cfi-vcall 37 + a->vf(); +} + +extern "C" __attribute__((noinline)) void nvcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] {{_?}}nvcall cfi-nvcall 51 + a->nvf(); +} + +extern "C" __attribute__((noinline)) A *dcast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] {{_?}}dcast cfi-derived-cast 24 + return (A *)(ABase *)a; +} + +extern "C" __attribute__((noinline)) A *ucast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] {{_?}}ucast cfi-unrelated-cast 81 + return (A *)(char *)a; +} + +extern "C" __attribute__((noinline)) void unreachable(A *a) { + // CHECK-NOT: unreachable + a->vf(); +} + +int main() { + A a; + for (unsigned i = 0; i != 37; ++i) + vcall(&a); + for (unsigned i = 0; i != 51; ++i) + nvcall(&a); + for (unsigned i = 0; i != 24; ++i) + dcast(&a); + for (unsigned i = 0; i != 81; ++i) + ucast(&a); + for (unsigned i = 0; i != 0; ++i) + unreachable(&a); +} diff --git a/test/cfi/target_uninstrumented.cpp b/test/cfi/target_uninstrumented.cpp new file mode 100644 index 000000000000..2ec2b5bbc978 --- /dev/null +++ b/test/cfi/target_uninstrumented.cpp @@ -0,0 +1,44 @@ +// RUN: %clangxx -g -DSHARED_LIB %s -fPIC -shared -o %T/target_uninstrumented-so.so +// RUN: %clangxx_cfi_diag -g %s -o %t %T/target_uninstrumented-so.so +// RUN: %t 2>&1 | FileCheck %s + +// REQUIRES: cxxabi + +#include <stdio.h> +#include <string.h> + +struct A { + virtual void f(); +}; + +void *create_B(); + +#ifdef SHARED_LIB + +struct B { + virtual void f(); +}; +void B::f() {} + +void *create_B() { + return (void *)(new B()); +} + +#else + +void A::f() {} + +int main(int argc, char *argv[]) { + void *p = create_B(); + // CHECK: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type + // CHECK: invalid vtable in module {{.*}}target_uninstrumented-so.so + A *a = (A *)p; + memset(p, 0, sizeof(A)); + // CHECK: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type + // CHECK-NOT: invalid vtable in module + // CHECK: invalid vtable + a = (A *)p; + // CHECK: done + fprintf(stderr, "done %p\n", a); +} +#endif |