aboutsummaryrefslogtreecommitdiff
path: root/test/cfi
diff options
context:
space:
mode:
Diffstat (limited to 'test/cfi')
-rw-r--r--test/cfi/CMakeLists.txt20
-rw-r--r--test/cfi/create-derivers.test8
-rw-r--r--test/cfi/cross-dso/dlopen.cpp147
-rw-r--r--test/cfi/cross-dso/icall/diag.cpp159
-rw-r--r--test/cfi/cross-dso/icall/icall-from-dso.cpp8
-rw-r--r--test/cfi/cross-dso/icall/icall.cpp8
-rw-r--r--test/cfi/cross-dso/shadow_is_read_only.cpp85
-rw-r--r--test/cfi/cross-dso/simple-fail.cpp9
-rw-r--r--test/cfi/cross-dso/stats.cpp59
-rw-r--r--test/cfi/cross-dso/target_out_of_bounds.cpp64
-rw-r--r--test/cfi/icall/bad-signature.c8
-rw-r--r--test/cfi/icall/external-call.c2
-rw-r--r--test/cfi/lit.cfg26
-rw-r--r--test/cfi/lit.site.cfg.in4
-rw-r--r--test/cfi/overwrite.cpp7
-rw-r--r--test/cfi/stats.cpp52
-rw-r--r--test/cfi/target_uninstrumented.cpp44
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