Add test suite for the Control Flow Integrity feature.
authorPeter Collingbourne <peter@pcc.me.uk>
Fri, 20 Feb 2015 20:31:18 +0000 (20:31 +0000)
committerPeter Collingbourne <peter@pcc.me.uk>
Fri, 20 Feb 2015 20:31:18 +0000 (20:31 +0000)
Differential Revision: http://reviews.llvm.org/D7738

llvm-svn: 230056

compiler-rt/test/CMakeLists.txt
compiler-rt/test/cfi/CMakeLists.txt [new file with mode: 0644]
compiler-rt/test/cfi/anon-namespace.cpp [new file with mode: 0644]
compiler-rt/test/cfi/lit.cfg [new file with mode: 0644]
compiler-rt/test/cfi/lit.site.cfg.in [new file with mode: 0644]
compiler-rt/test/cfi/multiple-inheritance.cpp [new file with mode: 0644]
compiler-rt/test/cfi/overwrite.cpp [new file with mode: 0644]
compiler-rt/test/cfi/simple-fail.cpp [new file with mode: 0644]
compiler-rt/test/cfi/simple-pass.cpp [new file with mode: 0644]
compiler-rt/test/cfi/vdtor.cpp [new file with mode: 0644]
compiler-rt/test/lit.common.configured.in

index dd3cbe9..85a1735 100644 (file)
@@ -57,6 +57,7 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
   if(COMPILER_RT_HAS_UBSAN)
     add_subdirectory(ubsan)
   endif()
+  add_subdirectory(cfi)
 endif()
 
 if(COMPILER_RT_STANDALONE_BUILD)
diff --git a/compiler-rt/test/cfi/CMakeLists.txt b/compiler-rt/test/cfi/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f519fb0
--- /dev/null
@@ -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/compiler-rt/test/cfi/anon-namespace.cpp b/compiler-rt/test/cfi/anon-namespace.cpp
new file mode 100644 (file)
index 0000000..f634ab3
--- /dev/null
@@ -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/compiler-rt/test/cfi/lit.cfg b/compiler-rt/test/cfi/lit.cfg
new file mode 100644 (file)
index 0000000..d78820d
--- /dev/null
@@ -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/compiler-rt/test/cfi/lit.site.cfg.in b/compiler-rt/test/cfi/lit.site.cfg.in
new file mode 100644 (file)
index 0000000..76897e7
--- /dev/null
@@ -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/compiler-rt/test/cfi/multiple-inheritance.cpp b/compiler-rt/test/cfi/multiple-inheritance.cpp
new file mode 100644 (file)
index 0000000..1b03af4
--- /dev/null
@@ -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/compiler-rt/test/cfi/overwrite.cpp b/compiler-rt/test/cfi/overwrite.cpp
new file mode 100644 (file)
index 0000000..74cb3fd
--- /dev/null
@@ -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/compiler-rt/test/cfi/simple-fail.cpp b/compiler-rt/test/cfi/simple-fail.cpp
new file mode 100644 (file)
index 0000000..de3b7ed
--- /dev/null
@@ -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/compiler-rt/test/cfi/simple-pass.cpp b/compiler-rt/test/cfi/simple-pass.cpp
new file mode 100644 (file)
index 0000000..e8eb393
--- /dev/null
@@ -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/compiler-rt/test/cfi/vdtor.cpp b/compiler-rt/test/cfi/vdtor.cpp
new file mode 100644 (file)
index 0000000..f8101ba
--- /dev/null
@@ -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");
+}
index ceab67d..4a5966e 100644 (file)
@@ -18,6 +18,8 @@ set_default("llvm_obj_root", "@LLVM_BINARY_DIR@")
 set_default("compiler_rt_src_root", "@COMPILER_RT_SOURCE_DIR@")
 set_default("compiler_rt_obj_root", "@COMPILER_RT_BINARY_DIR@")
 set_default("llvm_tools_dir", "@LLVM_TOOLS_DIR@")
+set_default("llvm_shlib_dir", "@SHLIBDIR@")
+set_default("gold_executable", "@GOLD_EXECUTABLE@")
 set_default("clang", "@COMPILER_RT_TEST_COMPILER@")
 set_default("compiler_id", "@COMPILER_RT_TEST_COMPILER_ID@")
 set_default("compiler_rt_arch", "@COMPILER_RT_SUPPORTED_ARCH@")