diff options
Diffstat (limited to 'test/cfi')
-rw-r--r-- | test/cfi/CMakeLists.txt | 23 | ||||
-rw-r--r-- | test/cfi/anon-namespace.cpp | 93 | ||||
-rw-r--r-- | test/cfi/lit.cfg | 35 | ||||
-rw-r--r-- | test/cfi/lit.site.cfg.in | 2 | ||||
-rw-r--r-- | test/cfi/multiple-inheritance.cpp | 82 | ||||
-rw-r--r-- | test/cfi/overwrite.cpp | 67 | ||||
-rw-r--r-- | test/cfi/simple-fail.cpp | 99 | ||||
-rw-r--r-- | test/cfi/simple-pass.cpp | 97 | ||||
-rw-r--r-- | test/cfi/utils.h | 53 | ||||
-rw-r--r-- | test/cfi/vdtor.cpp | 62 |
10 files changed, 613 insertions, 0 deletions
diff --git a/test/cfi/CMakeLists.txt b/test/cfi/CMakeLists.txt new file mode 100644 index 0000000000000..f519fb089505b --- /dev/null +++ b/test/cfi/CMakeLists.txt @@ -0,0 +1,23 @@ +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + +set(CFI_TEST_DEPS) +if(NOT COMPILER_RT_STANDALONE_BUILD) + list(APPEND CFI_TEST_DEPS + FileCheck + clang + not + ) + if(LLVM_ENABLE_PIC AND LLVM_BINUTILS_INCDIR) + list(APPEND CFI_TEST_DEPS + LLVMgold + ) + endif() +endif() + +add_lit_testsuite(check-cfi "Running the cfi regression tests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CFI_TEST_DEPS}) +set_target_properties(check-cfi PROPERTIES FOLDER "Tests") diff --git a/test/cfi/anon-namespace.cpp b/test/cfi/anon-namespace.cpp new file mode 100644 index 0000000000000..0c2c689966f14 --- /dev/null +++ b/test/cfi/anon-namespace.cpp @@ -0,0 +1,93 @@ +// RUN: %clangxx_cfi -c -DTU1 -o %t1.o %s +// RUN: %clangxx_cfi -c -DTU2 -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx_cfi -o %t %t1.o %t2.o +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -c -DTU1 -DB32 -o %t1.o %s +// RUN: %clangxx_cfi -c -DTU2 -DB32 -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx_cfi -o %t %t1.o %t2.o +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -c -DTU1 -DB64 -o %t1.o %s +// RUN: %clangxx_cfi -c -DTU2 -DB64 -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx_cfi -o %t %t1.o %t2.o +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -c -DTU1 -DBM -o %t1.o %s +// RUN: %clangxx_cfi -c -DTU2 -DBM -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx_cfi -o %t %t1.o %t2.o +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -c -DTU1 -o %t1.o %s +// RUN: %clangxx -c -DTU2 -o %t2.o %S/../cfi/anon-namespace.cpp +// RUN: %clangxx -o %t %t1.o %t2.o +// RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %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 +// anonymous namespace, and testing that the program crashes if TU2 attempts to +// use a TU1 B as a TU2 B. + +// FIXME: This test should not require that the paths supplied to the compiler +// are different. It currently does so because bitset names have global scope +// so we have to mangle the file path into the bitset name. + +#include <stdio.h> +#include "utils.h" + +struct A { + virtual void f() = 0; +}; + +namespace { + +struct B : A { + virtual void f() {} +}; + +} + +A *mkb(); + +#ifdef TU1 + +A *mkb() { + return new B; +} + +#endif // TU1 + +#ifdef TU2 + +int main() { +#ifdef B32 + break_optimization(new Deriver<B, 0>); +#endif + +#ifdef B64 + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); +#endif + +#ifdef BM + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); + break_optimization(new Deriver<B, 2>); +#endif + + A *a = mkb(); + break_optimization(a); + + // CFI: 1 + // NCFI: 1 + fprintf(stderr, "1\n"); + + ((B *)a)->f(); // UB here + + // CFI-NOT: 2 + // NCFI: 2 + fprintf(stderr, "2\n"); +} + +#endif // TU2 diff --git a/test/cfi/lit.cfg b/test/cfi/lit.cfg new file mode 100644 index 0000000000000..d78820daa055a --- /dev/null +++ b/test/cfi/lit.cfg @@ -0,0 +1,35 @@ +import lit.formats +import os +import subprocess +import sys + +config.name = 'cfi' +config.suffixes = ['.cpp'] +config.test_source_root = os.path.dirname(__file__) + +def is_darwin_lto_supported(): + return os.path.exists(os.path.join(config.llvm_shlib_dir, 'libLTO.dylib')) + +def is_linux_lto_supported(): + if not os.path.exists(os.path.join(config.llvm_shlib_dir, 'LLVMgold.so')): + return False + + ld_cmd = subprocess.Popen([config.gold_executable, '--help'], stdout = subprocess.PIPE) + ld_out = ld_cmd.stdout.read().decode() + ld_cmd.wait() + + if not '-plugin' in ld_out: + return False + + return True + +clangxx = ' '.join([config.clang] + config.cxx_mode_flags) + +config.substitutions.append((r"%clangxx ", clangxx + ' ')) + +if sys.platform == 'darwin' and is_darwin_lto_supported(): + config.substitutions.append((r"%clangxx_cfi ", 'env DYLD_LIBRARY_PATH=' + config.llvm_shlib_dir + ' ' + clangxx + ' -fsanitize=cfi ')) +elif sys.platform.startswith('linux') and is_linux_lto_supported(): + config.substitutions.append((r"%clangxx_cfi ", clangxx + ' -fuse-ld=gold -fsanitize=cfi ')) +else: + config.unsupported = True diff --git a/test/cfi/lit.site.cfg.in b/test/cfi/lit.site.cfg.in new file mode 100644 index 0000000000000..76897e7018745 --- /dev/null +++ b/test/cfi/lit.site.cfg.in @@ -0,0 +1,2 @@ +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/multiple-inheritance.cpp b/test/cfi/multiple-inheritance.cpp new file mode 100644 index 0000000000000..523af6f72f2fd --- /dev/null +++ b/test/cfi/multiple-inheritance.cpp @@ -0,0 +1,82 @@ +// RUN: %clangxx_cfi -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: not --crash %t x 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: not --crash %t x 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: not --crash %t x 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s +// RUN: not --crash %t x 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s +// RUN: %t x 2>&1 | FileCheck --check-prefix=NCFI %s + +// Tests that the CFI mechanism is sensitive to multiple inheritance and only +// permits calls via virtual tables for the correct base class. + +#include <stdio.h> +#include "utils.h" + +struct A { + virtual void f() = 0; +}; + +struct B { + virtual void g() = 0; +}; + +struct C : A, B { + virtual void f(), g(); +}; + +void C::f() {} +void C::g() {} + +int main(int argc, char **argv) { +#ifdef B32 + break_optimization(new Deriver<A, 0>); + break_optimization(new Deriver<B, 0>); +#endif + +#ifdef B64 + break_optimization(new Deriver<A, 0>); + break_optimization(new Deriver<A, 1>); + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); +#endif + +#ifdef BM + break_optimization(new Deriver<A, 0>); + break_optimization(new Deriver<A, 1>); + break_optimization(new Deriver<A, 2>); + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); + break_optimization(new Deriver<B, 2>); +#endif + + C *c = new C; + break_optimization(c); + + // CFI: 1 + // NCFI: 1 + fprintf(stderr, "1\n"); + + if (argc > 1) { + A *a = c; + ((B *)a)->g(); // UB here + } else { + B *b = c; + ((A *)b)->f(); // UB here + } + + // CFI-NOT: 2 + // NCFI: 2 + fprintf(stderr, "2\n"); +} diff --git a/test/cfi/overwrite.cpp b/test/cfi/overwrite.cpp new file mode 100644 index 0000000000000..d7e58d9277e95 --- /dev/null +++ b/test/cfi/overwrite.cpp @@ -0,0 +1,67 @@ +// RUN: %clangxx_cfi -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %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 +// attempting to make a call through it. + +#include <stdio.h> +#include "utils.h" + +struct A { + virtual void f(); +}; + +void A::f() {} + +void foo() { + fprintf(stderr, "foo\n"); +} + +void *fake_vtable[] = { (void *)&foo }; + +int main() { +#ifdef B32 + break_optimization(new Deriver<A, 0>); +#endif + +#ifdef B64 + break_optimization(new Deriver<A, 0>); + break_optimization(new Deriver<A, 1>); +#endif + +#ifdef BM + break_optimization(new Deriver<A, 0>); + break_optimization(new Deriver<A, 1>); + break_optimization(new Deriver<A, 2>); +#endif + + A *a = new A; + *((void **)a) = fake_vtable; // UB here + break_optimization(a); + + // CFI: 1 + // NCFI: 1 + fprintf(stderr, "1\n"); + + // CFI-NOT: foo + // NCFI: foo + a->f(); + + // CFI-NOT: 2 + // NCFI: 2 + fprintf(stderr, "2\n"); +} diff --git a/test/cfi/simple-fail.cpp b/test/cfi/simple-fail.cpp new file mode 100644 index 0000000000000..cf24f86e00649 --- /dev/null +++ b/test/cfi/simple-fail.cpp @@ -0,0 +1,99 @@ +// RUN: %clangxx_cfi -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O1 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O1 -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O1 -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O1 -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O2 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O2 -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O2 -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O2 -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O3 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O3 -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O3 -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -O3 -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %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. + +#include <stdio.h> +#include "utils.h" + +struct A { + virtual void f(); +}; + +void A::f() {} + +struct B { + virtual void f(); +}; + +void B::f() {} + +int main() { +#ifdef B32 + break_optimization(new Deriver<B, 0>); +#endif + +#ifdef B64 + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); +#endif + +#ifdef BM + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); + break_optimization(new Deriver<B, 2>); +#endif + + A *a = new A; + break_optimization(a); + + // CFI: 1 + // NCFI: 1 + fprintf(stderr, "1\n"); + + ((B *)a)->f(); // UB here + + // CFI-NOT: 2 + // NCFI: 2 + fprintf(stderr, "2\n"); +} diff --git a/test/cfi/simple-pass.cpp b/test/cfi/simple-pass.cpp new file mode 100644 index 0000000000000..50e7d9256084e --- /dev/null +++ b/test/cfi/simple-pass.cpp @@ -0,0 +1,97 @@ +// RUN: %clangxx_cfi -o %t %s +// RUN: %t + +// Tests that the CFI mechanism does not crash the program when making various +// kinds of valid calls involving classes with various different linkages and +// types of inheritance. + +#include "utils.h" + +struct A { + virtual void f(); +}; + +void A::f() {} + +struct A2 : A { + virtual void f(); +}; + +void A2::f() {} + +struct B { + virtual void f() {} +}; + +struct B2 : B { + virtual void f() {} +}; + +namespace { + +struct C { + virtual void f(); +}; + +void C::f() {} + +struct C2 : C { + virtual void f(); +}; + +void C2::f() {} + +struct D { + virtual void f() {} +}; + +struct D2 : D { + virtual void f() {} +}; + +} + +struct E { + virtual void f() {} +}; + +struct E2 : virtual E { + virtual void f() {} +}; + +int main() { + A *a = new A; + break_optimization(a); + a->f(); + a = new A2; + break_optimization(a); + a->f(); + + B *b = new B; + break_optimization(b); + b->f(); + b = new B2; + break_optimization(b); + b->f(); + + C *c = new C; + break_optimization(c); + c->f(); + c = new C2; + break_optimization(c); + c->f(); + + D *d = new D; + break_optimization(d); + d->f(); + d = new D2; + break_optimization(d); + d->f(); + + E *e = new E; + break_optimization(e); + e->f(); + e = new E2; + break_optimization(e); + e->f(); +} diff --git a/test/cfi/utils.h b/test/cfi/utils.h new file mode 100644 index 0000000000000..5c290d1510696 --- /dev/null +++ b/test/cfi/utils.h @@ -0,0 +1,53 @@ +#ifndef UTILS_H +#define UTILS_H + +inline void break_optimization(void *arg) { + __asm__ __volatile__("" : : "r" (arg) : "memory"); +} + +// Tests will instantiate this class to pad out bit sets to test out the various +// ways we can represent the bit set (32-bit inline, 64-bit inline, memory). +// This class has 37 virtual member functions, which forces us to use a +// pointer-aligned bitset. +template <typename T, unsigned I> +class Deriver : T { + virtual void f() {} + virtual void g() {} + virtual void f1() {} + virtual void f2() {} + virtual void f3() {} + virtual void f4() {} + virtual void f5() {} + virtual void f6() {} + virtual void f7() {} + virtual void f8() {} + virtual void f9() {} + virtual void f10() {} + virtual void f11() {} + virtual void f12() {} + virtual void f13() {} + virtual void f14() {} + virtual void f15() {} + virtual void f16() {} + virtual void f17() {} + virtual void f18() {} + virtual void f19() {} + virtual void f20() {} + virtual void f21() {} + virtual void f22() {} + virtual void f23() {} + virtual void f24() {} + virtual void f25() {} + virtual void f26() {} + virtual void f27() {} + virtual void f28() {} + virtual void f29() {} + virtual void f30() {} + virtual void f31() {} + virtual void f32() {} + virtual void f33() {} + virtual void f34() {} + virtual void f35() {} +}; + +#endif diff --git a/test/cfi/vdtor.cpp b/test/cfi/vdtor.cpp new file mode 100644 index 0000000000000..e21883c380dd0 --- /dev/null +++ b/test/cfi/vdtor.cpp @@ -0,0 +1,62 @@ +// RUN: %clangxx_cfi -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB32 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DB64 -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx_cfi -DBM -o %t %s +// RUN: not --crash %t 2>&1 | FileCheck --check-prefix=CFI %s + +// RUN: %clangxx -o %t %s +// RUN: %t 2>&1 | FileCheck --check-prefix=NCFI %s + +// Tests that the CFI enforcement also applies to virtual destructor calls made +// via 'delete'. + +#include <stdio.h> +#include "utils.h" + +struct A { + virtual ~A(); +}; + +A::~A() {} + +struct B { + virtual ~B(); +}; + +B::~B() {} + +int main() { +#ifdef B32 + break_optimization(new Deriver<B, 0>); +#endif + +#ifdef B64 + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); +#endif + +#ifdef BM + break_optimization(new Deriver<B, 0>); + break_optimization(new Deriver<B, 1>); + break_optimization(new Deriver<B, 2>); +#endif + + A *a = new A; + break_optimization(a); + + // CFI: 1 + // NCFI: 1 + fprintf(stderr, "1\n"); + + delete (B *)a; // UB here + + // CFI-NOT: 2 + // NCFI: 2 + fprintf(stderr, "2\n"); +} |