summaryrefslogtreecommitdiff
path: root/test/cfi
diff options
context:
space:
mode:
authorPeter Collingbourne <peter@pcc.me.uk>2015-02-20 20:31:18 +0000
committerPeter Collingbourne <peter@pcc.me.uk>2015-02-20 20:31:18 +0000
commit39a4a9d2feb820fecaf8a72066b25f335da5d627 (patch)
tree5c879a8c999aa1c713dd78c3cac3766b1feb0008 /test/cfi
parentae387957e63894c2618b9e404949b0a6b5a26843 (diff)
Add test suite for the Control Flow Integrity feature.
Differential Revision: http://reviews.llvm.org/D7738 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@230056 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'test/cfi')
-rw-r--r--test/cfi/CMakeLists.txt23
-rw-r--r--test/cfi/anon-namespace.cpp61
-rw-r--r--test/cfi/lit.cfg35
-rw-r--r--test/cfi/lit.site.cfg.in2
-rw-r--r--test/cfi/multiple-inheritance.cpp47
-rw-r--r--test/cfi/overwrite.cpp41
-rw-r--r--test/cfi/simple-fail.cpp37
-rw-r--r--test/cfi/simple-pass.cpp99
-rw-r--r--test/cfi/vdtor.cpp36
9 files changed, 381 insertions, 0 deletions
diff --git a/test/cfi/CMakeLists.txt b/test/cfi/CMakeLists.txt
new file mode 100644
index 000000000..f519fb089
--- /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 000000000..f634ab352
--- /dev/null
+++ b/test/cfi/anon-namespace.cpp
@@ -0,0 +1,61 @@
+// 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 -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>
+
+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() {
+ A *a = mkb();
+
+ // 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 000000000..d78820daa
--- /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 000000000..76897e701
--- /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 000000000..1b03af4a1
--- /dev/null
+++ b/test/cfi/multiple-inheritance.cpp
@@ -0,0 +1,47 @@
+// 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 -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>
+
+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) {
+ C *c = new 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 000000000..74cb3fdcb
--- /dev/null
+++ b/test/cfi/overwrite.cpp
@@ -0,0 +1,41 @@
+// RUN: %clangxx_cfi -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>
+
+struct A {
+ virtual void f();
+};
+
+void A::f() {}
+
+void foo() {
+ fprintf(stderr, "foo\n");
+}
+
+void *fake_vtable[] = { (void *)&foo };
+
+int main() {
+ A *a = new A;
+ *((void **)a) = fake_vtable; // UB here
+
+ // 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 000000000..de3b7ed1b
--- /dev/null
+++ b/test/cfi/simple-fail.cpp
@@ -0,0 +1,37 @@
+// RUN: %clangxx_cfi -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>
+
+struct A {
+ virtual void f();
+};
+
+void A::f() {}
+
+struct B {
+ virtual void f();
+};
+
+void B::f() {}
+
+int main() {
+ A *a = new 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 000000000..e8eb3939c
--- /dev/null
+++ b/test/cfi/simple-pass.cpp
@@ -0,0 +1,99 @@
+// 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.
+
+inline void break_optimization(void *arg) {
+ __asm__ __volatile__("" : : "r" (arg) : "memory");
+}
+
+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/vdtor.cpp b/test/cfi/vdtor.cpp
new file mode 100644
index 000000000..f8101bac8
--- /dev/null
+++ b/test/cfi/vdtor.cpp
@@ -0,0 +1,36 @@
+// RUN: %clangxx_cfi -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>
+
+struct A {
+ virtual ~A();
+};
+
+A::~A() {}
+
+struct B {
+ virtual ~B();
+};
+
+B::~B() {}
+
+int main() {
+ A *a = new A;
+
+ // CFI: 1
+ // NCFI: 1
+ fprintf(stderr, "1\n");
+
+ delete (B *)a; // UB here
+
+ // CFI-NOT: 2
+ // NCFI: 2
+ fprintf(stderr, "2\n");
+}