summaryrefslogtreecommitdiff
path: root/test/cfi
diff options
context:
space:
mode:
Diffstat (limited to 'test/cfi')
-rw-r--r--test/cfi/CMakeLists.txt23
-rw-r--r--test/cfi/anon-namespace.cpp93
-rw-r--r--test/cfi/lit.cfg35
-rw-r--r--test/cfi/lit.site.cfg.in2
-rw-r--r--test/cfi/multiple-inheritance.cpp82
-rw-r--r--test/cfi/overwrite.cpp67
-rw-r--r--test/cfi/simple-fail.cpp99
-rw-r--r--test/cfi/simple-pass.cpp97
-rw-r--r--test/cfi/utils.h53
-rw-r--r--test/cfi/vdtor.cpp62
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");
+}