[cfi] Safe handling of unaddressable vtable pointers (compiler-rt).
Avoid crashing when printing diagnostics for vtable-related CFI errors. In diagnostic mode, the frontend does an additional check of the vtable pointer against the set of all known vtable addresses and lets the runtime handler know if it is safe to inspect the vtable. http://reviews.llvm.org/D16824 llvm-svn: 259717
This commit is contained in:
parent
f31ea30694
commit
73583d5f2d
|
@ -307,7 +307,7 @@ ALWAYS_INLINE void CfiSlowPathCommon(u64 CallSiteTypeId, void *Ptr,
|
|||
#ifdef CFI_ENABLE_DIAG
|
||||
if (DiagData) {
|
||||
__ubsan_handle_cfi_check_fail(
|
||||
reinterpret_cast<__ubsan::CFICheckFailData *>(DiagData), Addr);
|
||||
reinterpret_cast<__ubsan::CFICheckFailData *>(DiagData), Addr, false);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -551,31 +551,33 @@ namespace __ubsan {
|
|||
#ifdef UBSAN_CAN_USE_CXXABI
|
||||
SANITIZER_WEAK_ATTRIBUTE
|
||||
void HandleCFIBadType(CFICheckFailData *Data, ValueHandle Vtable,
|
||||
ReportOptions Opts);
|
||||
bool ValidVtable, ReportOptions Opts);
|
||||
#else
|
||||
static void HandleCFIBadType(CFICheckFailData *Data, ValueHandle Vtable,
|
||||
ReportOptions Opts) {
|
||||
bool ValidVtable, ReportOptions Opts) {
|
||||
Die();
|
||||
}
|
||||
#endif
|
||||
} // namespace __ubsan
|
||||
|
||||
void __ubsan::__ubsan_handle_cfi_check_fail(CFICheckFailData *Data,
|
||||
ValueHandle Value) {
|
||||
ValueHandle Value,
|
||||
uptr ValidVtable) {
|
||||
GET_REPORT_OPTIONS(false);
|
||||
if (Data->CheckKind == CFITCK_ICall)
|
||||
handleCFIBadIcall(Data, Value, Opts);
|
||||
else
|
||||
HandleCFIBadType(Data, Value, Opts);
|
||||
HandleCFIBadType(Data, Value, ValidVtable, Opts);
|
||||
}
|
||||
|
||||
void __ubsan::__ubsan_handle_cfi_check_fail_abort(CFICheckFailData *Data,
|
||||
ValueHandle Value) {
|
||||
ValueHandle Value,
|
||||
uptr ValidVtable) {
|
||||
GET_REPORT_OPTIONS(true);
|
||||
if (Data->CheckKind == CFITCK_ICall)
|
||||
handleCFIBadIcall(Data, Value, Opts);
|
||||
else
|
||||
HandleCFIBadType(Data, Value, Opts);
|
||||
HandleCFIBadType(Data, Value, ValidVtable, Opts);
|
||||
Die();
|
||||
}
|
||||
|
||||
|
|
|
@ -165,8 +165,8 @@ struct CFICheckFailData {
|
|||
};
|
||||
|
||||
/// \brief Handle control flow integrity failures.
|
||||
RECOVERABLE(cfi_check_fail, CFICheckFailData *Data, ValueHandle Function)
|
||||
|
||||
RECOVERABLE(cfi_check_fail, CFICheckFailData *Data, ValueHandle Function,
|
||||
uptr VtableIsValid)
|
||||
}
|
||||
|
||||
#endif // UBSAN_HANDLERS_H
|
||||
|
|
|
@ -90,7 +90,7 @@ void __ubsan::__ubsan_handle_dynamic_type_cache_miss_abort(
|
|||
|
||||
namespace __ubsan {
|
||||
void HandleCFIBadType(CFICheckFailData *Data, ValueHandle Vtable,
|
||||
ReportOptions Opts) {
|
||||
bool ValidVtable, ReportOptions Opts) {
|
||||
SourceLocation Loc = Data->Loc.acquire();
|
||||
ErrorType ET = ErrorType::CFIBadType;
|
||||
|
||||
|
@ -98,7 +98,9 @@ void HandleCFIBadType(CFICheckFailData *Data, ValueHandle Vtable,
|
|||
return;
|
||||
|
||||
ScopedReport R(Opts, Loc, ET);
|
||||
DynamicTypeInfo DTI = getDynamicTypeInfoFromVtable((void *)Vtable);
|
||||
DynamicTypeInfo DTI = ValidVtable
|
||||
? getDynamicTypeInfoFromVtable((void *)Vtable)
|
||||
: DynamicTypeInfo(0, 0, 0);
|
||||
|
||||
const char *CheckKindStr;
|
||||
switch (Data->CheckKind) {
|
||||
|
@ -123,11 +125,16 @@ void HandleCFIBadType(CFICheckFailData *Data, ValueHandle Vtable,
|
|||
<< Data->Type << CheckKindStr << (void *)Vtable;
|
||||
|
||||
// If possible, say what type it actually points to.
|
||||
if (!DTI.isValid())
|
||||
Diag(Vtable, DL_Note, "invalid vtable");
|
||||
else
|
||||
if (!DTI.isValid()) {
|
||||
const char *module = Symbolizer::GetOrInit()->GetModuleNameForPc(Vtable);
|
||||
if (module)
|
||||
Diag(Vtable, DL_Note, "invalid vtable in module %0") << module;
|
||||
else
|
||||
Diag(Vtable, DL_Note, "invalid vtable");
|
||||
} else {
|
||||
Diag(Vtable, DL_Note, "vtable is of type %0")
|
||||
<< TypeName(DTI.getMostDerivedTypeName());
|
||||
}
|
||||
}
|
||||
} // namespace __ubsan
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
// RUN: %clangxx_cfi_dso_diag %s -o %t
|
||||
// RUN: %expect_crash %t 2>&1 | FileCheck %s
|
||||
// RUN: %clangxx_cfi_dso_diag -std=c++11 %s -o %t
|
||||
// RUN: %t zero 2>&1 | FileCheck --check-prefix=CHECK-ZERO %s
|
||||
// RUN: %t unaddressable 2>&1 | FileCheck --check-prefix=CHECK-UNADDR %s
|
||||
// RUN: %t 2>&1 | FileCheck --check-prefix=CHECK-TYPEINFO %s
|
||||
|
||||
// RUN: %clangxx_cfi_diag -std=c++11 %s -o %t2
|
||||
// RUN: %t2 zero 2>&1 | FileCheck --check-prefix=CHECK-ZERO %s
|
||||
// RUN: %t2 unaddressable 2>&1 | FileCheck --check-prefix=CHECK-UNADDR %s
|
||||
// RUN: %t2 2>&1 | FileCheck --check-prefix=CHECK-TYPEINFO %s
|
||||
|
||||
// REQUIRES: cxxabi
|
||||
|
||||
|
@ -7,6 +14,7 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
struct A {
|
||||
virtual void f();
|
||||
|
@ -15,18 +23,42 @@ struct A {
|
|||
void A::f() {}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Create an object with a vtable outside of any known DSO, but still in an
|
||||
// addressable area. Current implementation of handlers in UBSan is not robust
|
||||
// enough to handle unaddressable vtables. TODO: fix this.
|
||||
void *empty = calloc(1, 128);
|
||||
uintptr_t v = (uintptr_t)empty + 64;
|
||||
char *volatile p = reinterpret_cast<char *>(new A());
|
||||
for (uintptr_t *q = (uintptr_t *)p; q < (uintptr_t *)(p + sizeof(A)); ++q)
|
||||
*q = v;
|
||||
if (argc > 1 && strcmp(argv[1], "unaddressable") == 0) {
|
||||
void *vtable = mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
||||
// Create an object with a vtable in an unaddressable memory region.
|
||||
*(uintptr_t *)p = (uintptr_t)vtable + 64;
|
||||
// CHECK-UNADDR: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-UNADDR: note: invalid vtable
|
||||
// CHECK-UNADDR: <memory cannot be printed>
|
||||
// CHECK-UNADDR: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-UNADDR: note: invalid vtable
|
||||
// CHECK-UNADDR: <memory cannot be printed>
|
||||
} else if (argc > 1 && strcmp(argv[1], "zero") == 0) {
|
||||
// Create an object with a vtable outside of any known DSO, but still in an
|
||||
// addressable area.
|
||||
void *vtable = calloc(1, 128);
|
||||
*(uintptr_t *)p = (uintptr_t)vtable + 64;
|
||||
// CHECK-ZERO: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-ZERO: note: invalid vtable
|
||||
// CHECK-ZERO: 00 00 00 00 00 00 00 00
|
||||
// CHECK-ZERO: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-ZERO: note: invalid vtable
|
||||
// CHECK-ZERO: 00 00 00 00 00 00 00 00
|
||||
} else {
|
||||
// Create an object with a seemingly fine vtable, but with an unaddressable
|
||||
// typeinfo pointer.
|
||||
void *vtable = calloc(1, 128);
|
||||
memset(vtable, 0xFE, 128);
|
||||
*(uintptr_t *)p = (uintptr_t)vtable + 64;
|
||||
// CHECK-TYPEINFO: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-TYPEINFO: note: invalid vtable
|
||||
// CHECK-TYPEINFO: fe fe fe fe fe fe fe fe
|
||||
// CHECK-TYPEINFO: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
// CHECK-TYPEINFO: note: invalid vtable
|
||||
// CHECK-TYPEINFO: fe fe fe fe fe fe fe fe
|
||||
}
|
||||
|
||||
// CHECK: runtime error: control flow integrity check for type 'A' failed during cast
|
||||
A *volatile pa = reinterpret_cast<A *>(p);
|
||||
|
||||
// CHECK: untime error: control flow integrity check for type 'A' failed during virtual call
|
||||
pa->f();
|
||||
pa = reinterpret_cast<A *>(p);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// RUN: %clangxx -g -DSHARED_LIB %s -fPIC -shared -o %T/target_uninstrumented-so.so
|
||||
// RUN: %clangxx_cfi_diag -g %s -o %t %T/target_uninstrumented-so.so
|
||||
// RUN: %t 2>&1 | FileCheck %s
|
||||
|
||||
// REQUIRES: cxxabi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
struct A {
|
||||
virtual void f();
|
||||
};
|
||||
|
||||
void *create_B();
|
||||
|
||||
#ifdef SHARED_LIB
|
||||
|
||||
struct B {
|
||||
virtual void f();
|
||||
};
|
||||
void B::f() {}
|
||||
|
||||
void *create_B() {
|
||||
return (void *)(new B());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void A::f() {}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
void *p = create_B();
|
||||
// CHECK: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type
|
||||
// CHECK: invalid vtable in module {{.*}}target_uninstrumented-so.so
|
||||
A *a = (A *)p;
|
||||
memset(p, 0, sizeof(A));
|
||||
// CHECK: runtime error: control flow integrity check for type 'A' failed during cast to unrelated type
|
||||
// CHECK-NOT: invalid vtable in module
|
||||
// CHECK: invalid vtable
|
||||
a = (A *)p;
|
||||
// CHECK: done
|
||||
fprintf(stderr, "done %p\n", a);
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue