Add test suite for the Control Flow Integrity feature.

Differential Revision: http://reviews.llvm.org/D7738

llvm-svn: 230056
This commit is contained in:
Peter Collingbourne 2015-02-20 20:31:18 +00:00
parent a4ccff3281
commit e0c4f7eb81
11 changed files with 384 additions and 0 deletions

View 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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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();
}

View File

@ -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");
}

View 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@")