Skip to content

Commit

Permalink
Add test suite for the Control Flow Integrity feature.
Browse files Browse the repository at this point in the history
Differential Revision: http://reviews.llvm.org/D7738

llvm-svn: 230056
  • Loading branch information
pcc committed Feb 20, 2015
1 parent a4ccff3 commit e0c4f7e
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 0 deletions.
1 change: 1 addition & 0 deletions compiler-rt/test/CMakeLists.txt
Expand Up @@ -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)
Expand Down
23 changes: 23 additions & 0 deletions compiler-rt/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")
61 changes: 61 additions & 0 deletions compiler-rt/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
35 changes: 35 additions & 0 deletions compiler-rt/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
2 changes: 2 additions & 0 deletions compiler-rt/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")
47 changes: 47 additions & 0 deletions compiler-rt/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");
}
41 changes: 41 additions & 0 deletions compiler-rt/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");
}
37 changes: 37 additions & 0 deletions compiler-rt/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");
}
99 changes: 99 additions & 0 deletions compiler-rt/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();
}
36 changes: 36 additions & 0 deletions compiler-rt/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");
}

0 comments on commit e0c4f7e

Please sign in to comment.