summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/config-ix.cmake12
-rw-r--r--lib/CMakeLists.txt6
-rw-r--r--lib/cfi/CMakeLists.txt23
-rw-r--r--lib/cfi/cfi.cc265
-rw-r--r--test/cfi/CMakeLists.txt1
-rw-r--r--test/cfi/cross-dso/icall/icall-from-dso.cpp26
-rw-r--r--test/cfi/cross-dso/icall/icall.cpp21
-rw-r--r--test/cfi/cross-dso/icall/lit.local.cfg3
-rw-r--r--test/cfi/cross-dso/simple-fail.cpp87
-rw-r--r--test/cfi/cross-dso/simple-pass.cpp65
-rw-r--r--test/cfi/lit.cfg5
11 files changed, 511 insertions, 3 deletions
diff --git a/cmake/config-ix.cmake b/cmake/config-ix.cmake
index d003f03a7..f91530bb4 100644
--- a/cmake/config-ix.cmake
+++ b/cmake/config-ix.cmake
@@ -282,6 +282,7 @@ set(ALL_TSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${PPC64})
set(ALL_UBSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64}
${MIPS32} ${MIPS64} ${PPC64})
set(ALL_SAFESTACK_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM64})
+set(ALL_CFI_SUPPORTED_ARCH ${X86} ${X86_64})
if(APPLE)
include(CompilerRTDarwinUtils)
@@ -471,6 +472,9 @@ if(APPLE)
list_union(SAFESTACK_SUPPORTED_ARCH
ALL_SAFESTACK_SUPPORTED_ARCH
SANITIZER_COMMON_SUPPORTED_ARCH)
+ list_union(CFI_SUPPORTED_ARCH
+ ALL_CFI_SUPPORTED_ARCH
+ SANITIZER_COMMON_SUPPORTED_ARCH)
else()
# Architectures supported by compiler-rt libraries.
filter_available_targets(BUILTIN_SUPPORTED_ARCH
@@ -492,6 +496,7 @@ else()
filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH})
filter_available_targets(SAFESTACK_SUPPORTED_ARCH
${ALL_SAFESTACK_SUPPORTED_ARCH})
+ filter_available_targets(CFI_SUPPORTED_ARCH ${ALL_CFI_SUPPORTED_ARCH})
endif()
message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}")
@@ -580,3 +585,10 @@ if (COMPILER_RT_HAS_SANITIZER_COMMON AND SAFESTACK_SUPPORTED_ARCH AND
else()
set(COMPILER_RT_HAS_SAFESTACK FALSE)
endif()
+
+if (COMPILER_RT_HAS_SANITIZER_COMMON AND CFI_SUPPORTED_ARCH AND
+ OS_NAME MATCHES "Linux")
+ set(COMPILER_RT_HAS_CFI TRUE)
+else()
+ set(COMPILER_RT_HAS_CFI FALSE)
+endif()
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 9215b080b..4bc6f7a2d 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -19,8 +19,6 @@ if(COMPILER_RT_BUILD_SANITIZERS)
add_subdirectory(ubsan)
endif()
- add_subdirectory(cfi)
-
if(COMPILER_RT_HAS_ASAN)
add_subdirectory(asan)
endif()
@@ -45,4 +43,8 @@ if(COMPILER_RT_BUILD_SANITIZERS)
if(COMPILER_RT_HAS_SAFESTACK)
add_subdirectory(safestack)
endif()
+
+ if(COMPILER_RT_HAS_CFI)
+ add_subdirectory(cfi)
+ endif()
endif()
diff --git a/lib/cfi/CMakeLists.txt b/lib/cfi/CMakeLists.txt
index 90b66a84c..e441f09fb 100644
--- a/lib/cfi/CMakeLists.txt
+++ b/lib/cfi/CMakeLists.txt
@@ -1,4 +1,27 @@
add_custom_target(cfi)
+
+set(CFI_SOURCES cfi.cc)
+
+include_directories(..)
+
+set(CFI_CFLAGS
+ ${SANITIZER_COMMON_CFLAGS}
+)
+
+foreach(arch ${CFI_SUPPORTED_ARCH})
+ add_compiler_rt_runtime(clang_rt.cfi
+ STATIC
+ ARCHS ${arch}
+ SOURCES ${CFI_SOURCES}
+ OBJECT_LIBS RTInterception
+ RTSanitizerCommon
+ RTSanitizerCommonLibc
+ RTUbsan
+ RTUbsan_cxx
+ CFLAGS ${CFI_CFLAGS}
+ PARENT_TARGET cfi)
+endforeach()
+
add_compiler_rt_resource_file(cfi_blacklist cfi_blacklist.txt)
add_dependencies(cfi cfi_blacklist)
add_dependencies(compiler-rt cfi)
diff --git a/lib/cfi/cfi.cc b/lib/cfi/cfi.cc
new file mode 100644
index 000000000..27b745853
--- /dev/null
+++ b/lib/cfi/cfi.cc
@@ -0,0 +1,265 @@
+//===-------- cfi.cc ------------------------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the runtime support for the cross-DSO CFI.
+//
+//===----------------------------------------------------------------------===//
+
+// FIXME: Intercept dlopen/dlclose.
+// FIXME: Support diagnostic mode.
+// FIXME: Harden:
+// * mprotect shadow, use mremap for updates
+// * something else equally important
+
+#include <assert.h>
+#include <elf.h>
+#include <link.h>
+#include <string.h>
+
+typedef ElfW(Phdr) Elf_Phdr;
+typedef ElfW(Ehdr) Elf_Ehdr;
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "ubsan/ubsan_init.h"
+#include "ubsan/ubsan_flags.h"
+
+static uptr __cfi_shadow;
+static constexpr uptr kShadowGranularity = 12;
+static constexpr uptr kShadowAlign = 1UL << kShadowGranularity; // 4096
+
+static constexpr uint16_t kInvalidShadow = 0;
+static constexpr uint16_t kUncheckedShadow = 0xFFFFU;
+
+static uint16_t *mem_to_shadow(uptr x) {
+ return (uint16_t *)(__cfi_shadow + ((x >> kShadowGranularity) << 1));
+}
+
+typedef int (*CFICheckFn)(uptr, void *);
+
+class ShadowValue {
+ uptr addr;
+ uint16_t v;
+ explicit ShadowValue(uptr addr, uint16_t v) : addr(addr), v(v) {}
+
+public:
+ bool is_invalid() const { return v == kInvalidShadow; }
+
+ bool is_unchecked() const { return v == kUncheckedShadow; }
+
+ CFICheckFn get_cfi_check() const {
+ assert(!is_invalid() && !is_unchecked());
+ uptr aligned_addr = addr & ~(kShadowAlign - 1);
+ uptr p = aligned_addr - (((uptr)v - 1) << kShadowGranularity);
+ return reinterpret_cast<CFICheckFn>(p);
+ }
+
+ // Load a shadow valud for the given application memory address.
+ static const ShadowValue load(uptr addr) {
+ return ShadowValue(addr, *mem_to_shadow(addr));
+ }
+};
+
+static void fill_shadow_constant(uptr begin, uptr end, uint16_t v) {
+ assert(v == kInvalidShadow || v == kUncheckedShadow);
+ uint16_t *shadow_begin = mem_to_shadow(begin);
+ uint16_t *shadow_end = mem_to_shadow(end - 1) + 1;
+ memset(shadow_begin, v, (shadow_end - shadow_begin) * sizeof(*shadow_begin));
+}
+
+static void fill_shadow(uptr begin, uptr end, uptr cfi_check) {
+ assert((cfi_check & (kShadowAlign - 1)) == 0);
+
+ // Don't fill anything below cfi_check. We can not represent those addresses
+ // in the shadow, and must make sure at codegen to place all valid call
+ // targets above cfi_check.
+ uptr p = Max(begin, cfi_check);
+ uint16_t *s = mem_to_shadow(p);
+ uint16_t *s_end = mem_to_shadow(end - 1) + 1;
+ uint16_t sv = ((p - cfi_check) >> kShadowGranularity) + 1;
+ for (; s < s_end; s++, sv++)
+ *s = sv;
+
+ // Sanity checks.
+ for (; p < end; p += kShadowAlign) {
+ assert((uptr)ShadowValue::load(p).get_cfi_check() == cfi_check);
+ assert((uptr)ShadowValue::load(p + kShadowAlign / 2).get_cfi_check() ==
+ cfi_check);
+ assert((uptr)ShadowValue::load(p + kShadowAlign - 1).get_cfi_check() ==
+ cfi_check);
+ }
+}
+
+// This is a workaround for a glibc bug:
+// https://sourceware.org/bugzilla/show_bug.cgi?id=15199
+// Other platforms can, hopefully, just do
+// dlopen(RTLD_NOLOAD | RTLD_LAZY)
+// dlsym("__cfi_check").
+static uptr find_cfi_check_in_dso(dl_phdr_info *info) {
+ const ElfW(Dyn) *dynamic = nullptr;
+ for (int i = 0; i < info->dlpi_phnum; ++i) {
+ if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) {
+ dynamic =
+ (const ElfW(Dyn) *)(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr);
+ break;
+ }
+ }
+ if (!dynamic) return 0;
+ uptr strtab = 0, symtab = 0;
+ for (const ElfW(Dyn) *p = dynamic; p->d_tag != PT_NULL; ++p) {
+ if (p->d_tag == DT_SYMTAB)
+ symtab = p->d_un.d_ptr;
+ else if (p->d_tag == DT_STRTAB)
+ strtab = p->d_un.d_ptr;
+ }
+
+ if (symtab > strtab) {
+ VReport(1, "Can not handle: symtab > strtab (%p > %zx)\n", symtab, strtab);
+ return 0;
+ }
+
+ // Verify that strtab and symtab are inside of the same LOAD segment.
+ // This excludes VDSO, which has (very high) bogus strtab and symtab pointers.
+ int phdr_idx;
+ for (phdr_idx = 0; phdr_idx < info->dlpi_phnum; phdr_idx++) {
+ const Elf_Phdr *phdr = &info->dlpi_phdr[phdr_idx];
+ if (phdr->p_type == PT_LOAD) {
+ uptr beg = info->dlpi_addr + phdr->p_vaddr;
+ uptr end = beg + phdr->p_memsz;
+ if (strtab >= beg && strtab < end && symtab >= beg && symtab < end)
+ break;
+ }
+ }
+ if (phdr_idx == info->dlpi_phnum) {
+ // Nope, either different segments or just bogus pointers.
+ // Can not handle this.
+ VReport(1, "Can not handle: symtab %p, strtab %zx\n", symtab, strtab);
+ return 0;
+ }
+
+ for (const ElfW(Sym) *p = (const ElfW(Sym) *)symtab; (ElfW(Addr))p < strtab;
+ ++p) {
+ char *name = (char*)(strtab + p->st_name);
+ if (strcmp(name, "__cfi_check") == 0) {
+ assert(p->st_info == ELF32_ST_INFO(STB_GLOBAL, STT_FUNC));
+ uptr addr = info->dlpi_addr + p->st_value;
+ return addr;
+ }
+ }
+ return 0;
+}
+
+static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *data) {
+ uptr cfi_check = find_cfi_check_in_dso(info);
+ if (cfi_check)
+ VReport(1, "Module '%s' __cfi_check %zx\n", info->dlpi_name, cfi_check);
+
+ for (int i = 0; i < info->dlpi_phnum; i++) {
+ const Elf_Phdr *phdr = &info->dlpi_phdr[i];
+ if (phdr->p_type == PT_LOAD) {
+ // Jump tables are in the executable segment.
+ // VTables are in the non-executable one.
+ // Need to fill shadow for both.
+ // FIXME: reject writable if vtables are in the r/o segment. Depend on
+ // PT_RELRO?
+ uptr cur_beg = info->dlpi_addr + phdr->p_vaddr;
+ uptr cur_end = cur_beg + phdr->p_memsz;
+ if (cfi_check) {
+ VReport(1, " %zx .. %zx\n", cur_beg, cur_end);
+ fill_shadow(cur_beg, cur_end, cfi_check ? cfi_check : (uptr)(-1));
+ } else {
+ fill_shadow_constant(cur_beg, cur_end, kInvalidShadow);
+ }
+ }
+ }
+ return 0;
+}
+
+// Fill shadow for the initial libraries.
+static void init_shadow() {
+ dl_iterate_phdr(dl_iterate_phdr_cb, nullptr);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE extern "C"
+void __cfi_slowpath(uptr CallSiteTypeId, void *Ptr) {
+ uptr Addr = (uptr)Ptr;
+ VReport(3, "__cfi_slowpath: %zx, %p\n", CallSiteTypeId, Ptr);
+ ShadowValue sv = ShadowValue::load(Addr);
+ if (sv.is_invalid()) {
+ VReport(2, "CFI: invalid memory region for a function pointer (shadow==0): %p\n", Ptr);
+ Die();
+ }
+ if (sv.is_unchecked()) {
+ VReport(2, "CFI: unchecked call (shadow=FFFF): %p\n", Ptr);
+ return;
+ }
+ CFICheckFn cfi_check = sv.get_cfi_check();
+ VReport(2, "__cfi_check at %p\n", cfi_check);
+ cfi_check(CallSiteTypeId, Ptr);
+}
+
+static void InitializeFlags() {
+ SetCommonFlagsDefaults();
+ __ubsan::Flags *uf = __ubsan::flags();
+ uf->SetDefaults();
+
+ FlagParser cfi_parser;
+ RegisterCommonFlags(&cfi_parser);
+
+ FlagParser ubsan_parser;
+ __ubsan::RegisterUbsanFlags(&ubsan_parser, uf);
+ RegisterCommonFlags(&ubsan_parser);
+
+ const char *ubsan_default_options = __ubsan::MaybeCallUbsanDefaultOptions();
+ ubsan_parser.ParseString(ubsan_default_options);
+
+ cfi_parser.ParseString(GetEnv("CFI_OPTIONS"));
+ ubsan_parser.ParseString(GetEnv("UBSAN_OPTIONS"));
+
+ SetVerbosity(common_flags()->verbosity);
+
+ if (Verbosity()) ReportUnrecognizedFlags();
+
+ if (common_flags()->help) {
+ cfi_parser.PrintFlagDescriptions();
+ }
+}
+
+extern "C" __attribute__((visibility("default")))
+#if !SANITIZER_CAN_USE_PREINIT_ARRAY
+// On ELF platforms, the constructor is invoked using .preinit_array (see below)
+__attribute__((constructor(0)))
+#endif
+void __cfi_init() {
+ SanitizerToolName = "CFI";
+ InitializeFlags();
+
+ uptr vma = GetMaxVirtualAddress();
+ // Shadow is 2 -> 2**kShadowGranularity.
+ uptr shadow_size = (vma >> (kShadowGranularity - 1)) + 1;
+ VReport(1, "CFI: VMA size %zx, shadow size %zx\n", vma, shadow_size);
+ void *shadow = MmapNoReserveOrDie(shadow_size, "CFI shadow");
+ VReport(1, "CFI: shadow at %zx .. %zx\n", shadow,
+ reinterpret_cast<uptr>(shadow) + shadow_size);
+ __cfi_shadow = (uptr)shadow;
+ init_shadow();
+
+ __ubsan::InitAsPlugin();
+}
+
+#if SANITIZER_CAN_USE_PREINIT_ARRAY
+// On ELF platforms, run cfi initialization before any other constructors.
+// On other platforms we use the constructor attribute to arrange to run our
+// initialization early.
+extern "C" {
+__attribute__((section(".preinit_array"),
+ used)) void (*__cfi_preinit)(void) = __cfi_init;
+}
+#endif
diff --git a/test/cfi/CMakeLists.txt b/test/cfi/CMakeLists.txt
index 09672953b..c4f7eb92c 100644
--- a/test/cfi/CMakeLists.txt
+++ b/test/cfi/CMakeLists.txt
@@ -6,6 +6,7 @@ configure_lit_site_cfg(
set(CFI_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
if(NOT COMPILER_RT_STANDALONE_BUILD)
list(APPEND CFI_TEST_DEPS
+ cfi
opt
ubsan
)
diff --git a/test/cfi/cross-dso/icall/icall-from-dso.cpp b/test/cfi/cross-dso/icall/icall-from-dso.cpp
new file mode 100644
index 000000000..1995f05f4
--- /dev/null
+++ b/test/cfi/cross-dso/icall/icall-from-dso.cpp
@@ -0,0 +1,26 @@
+// RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t-so.so
+// RUN: %clangxx_cfi_dso %s -o %t %t-so.so && %expect_crash %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+
+#ifdef SHARED_LIB
+void g();
+void f() {
+ // CHECK: =1=
+ fprintf(stderr, "=1=\n");
+ ((void (*)(void))g)();
+ // CHECK: =2=
+ fprintf(stderr, "=2=\n");
+ ((void (*)(int))g)(42); // UB here
+ // CHECK-NOT: =3=
+ fprintf(stderr, "=3=\n");
+}
+#else
+void f();
+void g() {
+}
+
+int main() {
+ f();
+}
+#endif
diff --git a/test/cfi/cross-dso/icall/icall.cpp b/test/cfi/cross-dso/icall/icall.cpp
new file mode 100644
index 000000000..d7cc2f9ca
--- /dev/null
+++ b/test/cfi/cross-dso/icall/icall.cpp
@@ -0,0 +1,21 @@
+// RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t-so.so
+// RUN: %clangxx_cfi_dso %s -o %t %t-so.so && %expect_crash %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+
+#ifdef SHARED_LIB
+void f() {
+}
+#else
+void f();
+int main() {
+ // CHECK: =1=
+ fprintf(stderr, "=1=\n");
+ ((void (*)(void))f)();
+ // CHECK: =2=
+ fprintf(stderr, "=2=\n");
+ ((void (*)(int))f)(42); // UB here
+ // CHECK-NOT: =3=
+ fprintf(stderr, "=3=\n");
+}
+#endif
diff --git a/test/cfi/cross-dso/icall/lit.local.cfg b/test/cfi/cross-dso/icall/lit.local.cfg
new file mode 100644
index 000000000..db08765a2
--- /dev/null
+++ b/test/cfi/cross-dso/icall/lit.local.cfg
@@ -0,0 +1,3 @@
+# The cfi-icall checker is only supported on x86 and x86_64 for now.
+if config.root.host_arch not in ['x86', 'x86_64']:
+ config.unsupported = True
diff --git a/test/cfi/cross-dso/simple-fail.cpp b/test/cfi/cross-dso/simple-fail.cpp
new file mode 100644
index 000000000..dda57d2a8
--- /dev/null
+++ b/test/cfi/cross-dso/simple-fail.cpp
@@ -0,0 +1,87 @@
+// RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t1-so.so
+// RUN: %clangxx_cfi_dso %s -o %t1 %t1-so.so
+// RUN: %expect_crash %t1 2>&1 | FileCheck --check-prefix=CFI %s
+// RUN: %expect_crash %t1 x 2>&1 | FileCheck --check-prefix=CFI-CAST %s
+
+// RUN: %clangxx_cfi_dso -DB32 -DSHARED_LIB %s -fPIC -shared -o %t2-so.so
+// RUN: %clangxx_cfi_dso -DB32 %s -o %t2 %t2-so.so
+// RUN: %expect_crash %t2 2>&1 | FileCheck --check-prefix=CFI %s
+// RUN: %expect_crash %t2 x 2>&1 | FileCheck --check-prefix=CFI-CAST %s
+
+// RUN: %clangxx_cfi_dso -DB64 -DSHARED_LIB %s -fPIC -shared -o %t3-so.so
+// RUN: %clangxx_cfi_dso -DB64 %s -o %t3 %t3-so.so
+// RUN: %expect_crash %t3 2>&1 | FileCheck --check-prefix=CFI %s
+// RUN: %expect_crash %t3 x 2>&1 | FileCheck --check-prefix=CFI-CAST %s
+
+// RUN: %clangxx_cfi_dso -DBM -DSHARED_LIB %s -fPIC -shared -o %t4-so.so
+// RUN: %clangxx_cfi_dso -DBM %s -o %t4 %t4-so.so
+// RUN: %expect_crash %t4 2>&1 | FileCheck --check-prefix=CFI %s
+// RUN: %expect_crash %t4 x 2>&1 | FileCheck --check-prefix=CFI-CAST %s
+
+// RUN: %clangxx -DBM -DSHARED_LIB %s -fPIC -shared -o %t5-so.so
+// RUN: %clangxx -DBM %s -o %t5 %t5-so.so
+// RUN: %t5 2>&1 | FileCheck --check-prefix=NCFI %s
+// RUN: %t5 x 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.
+
+// REQUIRES: cxxabi
+
+#include <stdio.h>
+#include <string.h>
+
+struct A {
+ virtual void f();
+};
+
+void *create_B();
+
+#ifdef SHARED_LIB
+
+#include "../utils.h"
+struct B {
+ virtual void f();
+};
+void B::f() {}
+
+void *create_B() {
+ create_derivers<B>();
+ return (void *)(new B());
+}
+
+#else
+
+void A::f() {}
+
+int main(int argc, char *argv[]) {
+ void *p = create_B();
+ A *a;
+
+ // CFI: =0=
+ // CFI-CAST: =0=
+ // NCFI: =0=
+ fprintf(stderr, "=0=\n");
+
+ if (argc > 1 && argv[1][0] == 'x') {
+ // Test cast. BOOM.
+ a = (A*)p;
+ } else {
+ // Invisible to CFI. Test virtual call later.
+ memcpy(&a, &p, sizeof(a));
+ }
+
+ // CFI: =1=
+ // CFI-CAST-NOT: =1=
+ // NCFI: =1=
+ fprintf(stderr, "=1=\n");
+
+ a->f(); // UB here
+
+ // CFI-NOT: =2=
+ // CFI-CAST-NOT: =2=
+ // NCFI: =2=
+ fprintf(stderr, "=2=\n");
+}
+#endif
diff --git a/test/cfi/cross-dso/simple-pass.cpp b/test/cfi/cross-dso/simple-pass.cpp
new file mode 100644
index 000000000..42f7a2734
--- /dev/null
+++ b/test/cfi/cross-dso/simple-pass.cpp
@@ -0,0 +1,65 @@
+// RUN: %clangxx_cfi_dso -DSHARED_LIB %s -fPIC -shared -o %t1-so.so
+// RUN: %clangxx_cfi_dso %s -o %t1 %t1-so.so
+// RUN: %t1 2>&1 | FileCheck --check-prefix=CFI %s
+
+// RUN: %clangxx_cfi_dso -DB32 -DSHARED_LIB %s -fPIC -shared -o %t2-so.so
+// RUN: %clangxx_cfi_dso -DB32 %s -o %t2 %t2-so.so
+// RUN: %t2 2>&1 | FileCheck --check-prefix=CFI %s
+
+// RUN: %clangxx_cfi_dso -DB64 -DSHARED_LIB %s -fPIC -shared -o %t3-so.so
+// RUN: %clangxx_cfi_dso -DB64 %s -o %t3 %t3-so.so
+// RUN: %t3 2>&1 | FileCheck --check-prefix=CFI %s
+
+// RUN: %clangxx_cfi_dso -DBM -DSHARED_LIB %s -fPIC -shared -o %t4-so.so
+// RUN: %clangxx_cfi_dso -DBM %s -o %t4 %t4-so.so
+// RUN: %t4 2>&1 | FileCheck --check-prefix=CFI %s
+
+// RUN: %clangxx -DBM -DSHARED_LIB %s -fPIC -shared -o %t5-so.so
+// RUN: %clangxx -DBM %s -o %t5 %t5-so.so
+// RUN: %t5 2>&1 | FileCheck --check-prefix=NCFI %s
+// RUN: %t5 x 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.
+
+// REQUIRES: cxxabi
+
+#include <stdio.h>
+#include <string.h>
+
+struct A {
+ virtual void f();
+};
+
+A *create_B();
+
+#ifdef SHARED_LIB
+
+#include "../utils.h"
+struct B : public A {
+ virtual void f();
+};
+void B::f() {}
+
+A *create_B() {
+ create_derivers<B>();
+ return new B();
+}
+
+#else
+
+void A::f() {}
+
+int main(int argc, char *argv[]) {
+ A *a = create_B();
+
+ // CFI: =1=
+ // NCFI: =1=
+ fprintf(stderr, "=1=\n");
+ a->f(); // OK
+ // CFI: =2=
+ // NCFI: =2=
+ fprintf(stderr, "=2=\n");
+}
+#endif
diff --git a/test/cfi/lit.cfg b/test/cfi/lit.cfg
index a9b8992ea..687c80f4f 100644
--- a/test/cfi/lit.cfg
+++ b/test/cfi/lit.cfg
@@ -10,8 +10,11 @@ clangxx = ' '.join([config.clang] + config.cxx_mode_flags)
config.substitutions.append((r"%clangxx ", clangxx + ' '))
if config.lto_supported:
clangxx_cfi = ' '.join(config.lto_launch + [clangxx] + config.lto_flags + ['-flto -fsanitize=cfi '])
+ clangxx_cfi_diag = clangxx_cfi + '-fno-sanitize-trap=cfi -fsanitize-recover=cfi '
config.substitutions.append((r"%clangxx_cfi ", clangxx_cfi))
- config.substitutions.append((r"%clangxx_cfi_diag ", clangxx_cfi + '-fno-sanitize-trap=cfi -fsanitize-recover=cfi '))
+ config.substitutions.append((r"%clangxx_cfi_diag ", clangxx_cfi_diag))
+ config.substitutions.append((r"%clangxx_cfi_dso ", clangxx_cfi + '-fsanitize-cfi-cross-dso '))
+ config.substitutions.append((r"%clangxx_cfi_dso_diag ", clangxx_cfi_diag + '-fsanitize-cfi-cross-dso '))
else:
config.unsupported = True