Runtime support for CFI
Control Flow Integrity support in bionic.
General design:
http://clang.llvm.org/docs/ControlFlowIntegrityDesign.html#shared-library-support
This CL implements subsections "CFI Shadow" and "CFI_SlowPath" in the above document.
Bug: 22033465
Test: bionic device tests
Change-Id: I14dfea630de468eb5620e7f55f92b1397ba06217
diff --git a/tests/Android.bp b/tests/Android.bp
index 84be7bc..564ef03 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -295,6 +295,7 @@
target: {
android: {
srcs: [
+ "cfi_test.cpp",
"dlext_test.cpp",
"libdl_test.cpp",
],
diff --git a/tests/cfi_test.cpp b/tests/cfi_test.cpp
new file mode 100644
index 0000000..0f93edb
--- /dev/null
+++ b/tests/cfi_test.cpp
@@ -0,0 +1,94 @@
+#include <gtest/gtest.h>
+#include <dlfcn.h>
+
+#include "BionicDeathTest.h"
+
+// Private libdl interface.
+extern "C" {
+void __cfi_slowpath(uint64_t CallSiteTypeId, void* Ptr);
+void __cfi_slowpath_diag(uint64_t CallSiteTypeId, void* Ptr, void* DiagData);
+}
+
+static void f() {}
+
+TEST(cfi_test, basic) {
+ void* handle;
+ handle = dlopen("libcfi-test.so", RTLD_NOW | RTLD_LOCAL);
+ ASSERT_TRUE(handle != nullptr) << dlerror();
+
+#define SYM(type, name) auto name = reinterpret_cast<type>(dlsym(handle, #name))
+ SYM(int (*)(), get_count);
+ SYM(uint64_t(*)(), get_last_type_id);
+ SYM(void* (*)(), get_last_address);
+ SYM(void* (*)(), get_last_diag);
+ SYM(void* (*)(), get_global_address);
+ SYM(void (*)(uint64_t, void*, void*), __cfi_check);
+#undef SYM
+
+ int c = get_count();
+
+ // CFI check for code inside the DSO. Can't use just any function address - this is only
+ // guaranteed to work for code addresses above __cfi_check.
+ void* code_ptr = reinterpret_cast<char*>(__cfi_check) + 1234;
+ void* diag_ptr = reinterpret_cast<void*>(5678);
+ __cfi_slowpath_diag(42, code_ptr, diag_ptr);
+ EXPECT_EQ(42U, get_last_type_id());
+ EXPECT_EQ(code_ptr, get_last_address());
+ EXPECT_EQ(diag_ptr, get_last_diag());
+ EXPECT_EQ(++c, get_count());
+
+ // __cfi_slowpath passes nullptr for the Diag argument.
+ __cfi_slowpath(42, code_ptr);
+ EXPECT_EQ(42U, get_last_type_id());
+ EXPECT_EQ(code_ptr, get_last_address());
+ EXPECT_EQ(nullptr, get_last_diag());
+ EXPECT_EQ(++c, get_count());
+
+ // CFI check for a data address inside the DSO.
+ __cfi_slowpath(43, get_global_address());
+ EXPECT_EQ(43U, get_last_type_id());
+ EXPECT_EQ(get_global_address(), get_last_address());
+ EXPECT_EQ(++c, get_count());
+
+ // CFI check for a function inside _this_ DSO. It either goes to this DSO's __cfi_check,
+ // or (if missing) is simply ignored. Any way, it does not affect the test lib's counters.
+ __cfi_slowpath(44, reinterpret_cast<void*>(&f));
+ EXPECT_EQ(43U, get_last_type_id());
+ EXPECT_EQ(get_global_address(), get_last_address());
+ EXPECT_EQ(c, get_count());
+
+ // CFI check for a stack address. This is always invalid and gets the process killed.
+ EXPECT_DEATH(__cfi_slowpath(45, reinterpret_cast<void*>(&c)), "");
+
+ // CFI check for a heap address. This is always invalid and gets the process killed.
+ void* p = malloc(4096);
+ EXPECT_DEATH(__cfi_slowpath(46, p), "");
+ free(p);
+
+ // Load the same library again.
+ void* handle2 = dlopen("libcfi-test.so", RTLD_NOW | RTLD_LOCAL);
+ ASSERT_TRUE(handle2 != nullptr) << dlerror();
+ EXPECT_EQ(handle2, handle);
+
+ // Check that it is still there.
+ __cfi_slowpath(43, get_global_address());
+ EXPECT_EQ(43U, get_last_type_id());
+ EXPECT_EQ(get_global_address(), get_last_address());
+ EXPECT_EQ(++c, get_count());
+
+ dlclose(handle);
+ dlclose(handle2);
+
+ // CFI check for a function inside the unloaded DSO. This is always invalid and gets the process
+ // killed.
+ EXPECT_DEATH(__cfi_slowpath(45, reinterpret_cast<void*>(code_ptr)), "");
+}
+
+TEST(cfi_test, invalid) {
+ void* handle;
+ handle = dlopen("libcfi-test-bad.so", RTLD_NOW | RTLD_LOCAL);
+ ASSERT_FALSE(handle != nullptr) << dlerror();
+
+ handle = dlopen("libcfi-test-bad.so", RTLD_NOW | RTLD_LOCAL);
+ ASSERT_FALSE(handle != nullptr) << dlerror();
+}
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 4cd991a..330f17e 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -24,7 +24,8 @@
relative_install_path: "bionic-loader-test-libs",
gtest: false,
sanitize: {
- never: true,
+ address: false,
+ coverage: false,
},
target: {
darwin: {
@@ -474,3 +475,21 @@
"libutils",
],
}
+
+cc_test_library {
+ name: "libcfi-test",
+ defaults: ["bionic_testlib_defaults"],
+ srcs: ["cfi_test_lib.cpp"],
+ sanitize: {
+ cfi: false,
+ },
+}
+
+cc_test_library {
+ name: "libcfi-test-bad",
+ defaults: ["bionic_testlib_defaults"],
+ srcs: ["cfi_test_bad_lib.cpp"],
+ sanitize: {
+ cfi: false,
+ },
+}
diff --git a/tests/libs/cfi_test_bad_lib.cpp b/tests/libs/cfi_test_bad_lib.cpp
new file mode 100644
index 0000000..429c843
--- /dev/null
+++ b/tests/libs/cfi_test_bad_lib.cpp
@@ -0,0 +1,4 @@
+// Mock an invalid CFI-enabled library.
+__attribute__((aligned(4096))) extern "C" char dummy[16] = {};
+__asm__(".globl __cfi_check");
+__asm__("__cfi_check = dummy + 3"); // Not aligned to anything.
diff --git a/tests/libs/cfi_test_lib.cpp b/tests/libs/cfi_test_lib.cpp
new file mode 100644
index 0000000..b0e2f42
--- /dev/null
+++ b/tests/libs/cfi_test_lib.cpp
@@ -0,0 +1,68 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+// This library is built for all targets, including host tests, so __cfi_slowpath may not be
+// present. But it is only used in the bionic loader tests.
+extern "C" __attribute__((weak)) void __cfi_slowpath(uint64_t, void*);
+
+static int g_count;
+static uint64_t g_last_type_id;
+static void* g_last_address;
+static void* g_last_diag;
+
+extern "C" {
+
+// Mock a CFI-enabled library without relying on the compiler.
+__attribute__((aligned(4096))) void __cfi_check(uint64_t CallSiteTypeId, void* TargetAddr,
+ void* Diag) {
+ ++g_count;
+ g_last_type_id = CallSiteTypeId;
+ g_last_address = TargetAddr;
+ g_last_diag = Diag;
+}
+
+int get_count() {
+ return g_count;
+}
+
+uint64_t get_last_type_id() {
+ return g_last_type_id;
+}
+
+void* get_last_address() {
+ return g_last_address;
+}
+
+void* get_last_diag() {
+ return g_last_diag;
+}
+
+void* get_global_address() {
+ return &g_count;
+}
+}
+
+// Check that CFI is set up in module constructors and destructors.
+struct A {
+ void check_cfi_self() {
+ g_last_type_id = 0;
+ assert(&__cfi_slowpath);
+ // CFI check for an invalid address. Normally, this would kill the process by routing the call
+ // back to the calling module's __cfi_check, which does the right thing based on
+ // -fsanitize-recover / -fsanitize-trap. But this module has custom __cfi_check that does not do
+ // any of that, so the result looks like a passing check.
+ int zz;
+ __cfi_slowpath(13, static_cast<void*>(&zz));
+ assert(g_last_type_id == 13);
+ // CFI check for a libc function. This never goes into this module's __cfi_check, and must pass.
+ __cfi_slowpath(14, reinterpret_cast<void*>(&exit));
+ assert(g_last_type_id == 13);
+ }
+ A() {
+ check_cfi_self();
+ }
+ ~A() {
+ check_cfi_self();
+ }
+} a;