summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Collingbourne <peter@pcc.me.uk>2015-09-11 22:18:35 +0000
committerPeter Collingbourne <peter@pcc.me.uk>2015-09-11 22:18:35 +0000
commit69470be43161edab85b51b37b93dff25e44e4057 (patch)
tree99f0d4f20b971ed8d31b067fcf205e160f1f3dbe
parent00f3423844e696819bb02b547474804b8a4f9177 (diff)
ubsan: Implement memory permission validation for vtables.
If the pointer passed to the getVtablePrefix function was read from a freed object, we may end up following pointers into objects on the heap and printing bogus dynamic type names in diagnostics. However, we know that vtable pointers will generally only point into memory mapped from object files, not objects on the heap. This change causes us to only follow pointers in a vtable if the vtable and one of the virtual functions it points to appear to have appropriate permissions (i.e. non-writable, and maybe executable), which will generally exclude heap pointers. Only enabled for Linux; this hasn't been tested on FreeBSD, and vtables are writable on Mac (PR24782) so this won't work there. Differential Revision: http://reviews.llvm.org/D12790 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@247484 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--lib/ubsan/ubsan_type_hash_itanium.cc39
-rw-r--r--test/ubsan/TestCases/TypeCheck/vptr-bad-perms.cpp33
-rw-r--r--test/ubsan/lit.common.cfg3
3 files changed, 75 insertions, 0 deletions
diff --git a/lib/ubsan/ubsan_type_hash_itanium.cc b/lib/ubsan/ubsan_type_hash_itanium.cc
index b84e88d4c..170da2cdb 100644
--- a/lib/ubsan/ubsan_type_hash_itanium.cc
+++ b/lib/ubsan/ubsan_type_hash_itanium.cc
@@ -17,6 +17,7 @@
#include "ubsan_type_hash.h"
#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_procmaps.h"
// The following are intended to be binary compatible with the definitions
// given in the Itanium ABI. We make no attempt to be ODR-compatible with
@@ -191,7 +192,45 @@ struct VtablePrefix {
/// The type_info object describing the most-derived class type.
std::type_info *TypeInfo;
};
+
+#if SANITIZER_LINUX
+bool isValidVptr(void *Vtable) {
+ // Validate the memory permissions of the vtable pointer and the first
+ // function pointer in the vtable. They should be r-- or r-x and r-x
+ // respectively. Only enabled for Linux; this hasn't been tested on FreeBSD,
+ // and vtables are writable on Mac (PR24782) so this won't work there.
+ uptr FirstFunctionPtr = *reinterpret_cast<uptr *>(Vtable);
+ bool ValidVtable = false, ValidFirstFunctionPtr = false;
+ MemoryMappingLayout Layout(/*cache_enabled=*/true);
+ uptr Start, End, Prot;
+ while (Layout.Next(&Start, &End, 0, 0, 0, &Prot)) {
+ if (Start <= ((uptr)Vtable) && ((uptr)Vtable) <= End &&
+ (Prot == MemoryMappingLayout::kProtectionRead ||
+ Prot == (MemoryMappingLayout::kProtectionRead |
+ MemoryMappingLayout::kProtectionExecute)))
+ ValidVtable = true;
+ if (Start <= FirstFunctionPtr && FirstFunctionPtr <= End &&
+ Prot == (MemoryMappingLayout::kProtectionRead |
+ MemoryMappingLayout::kProtectionExecute))
+ ValidFirstFunctionPtr = true;
+ if (ValidVtable && ValidFirstFunctionPtr)
+ return true;
+ }
+ return false;
+}
+#else // !SANITIZER_LINUX
+bool isValidVptr(void *Vtable) {
+ return true;
+}
+#endif
+
VtablePrefix *getVtablePrefix(void *Vtable) {
+ if (!IsAccessibleMemoryRange((uptr)Vtable, sizeof(void *)))
+ return 0;
+
+ if (!isValidVptr(Vtable))
+ return 0;
+
VtablePrefix *Vptr = reinterpret_cast<VtablePrefix*>(Vtable);
if (!Vptr)
return 0;
diff --git a/test/ubsan/TestCases/TypeCheck/vptr-bad-perms.cpp b/test/ubsan/TestCases/TypeCheck/vptr-bad-perms.cpp
new file mode 100644
index 000000000..ad0d3b12a
--- /dev/null
+++ b/test/ubsan/TestCases/TypeCheck/vptr-bad-perms.cpp
@@ -0,0 +1,33 @@
+// RUN: %clangxx -frtti -fsanitize=vptr -fno-sanitize-recover=vptr -g %s -O3 -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+// Tests that we consider vtable pointers in writable memory to be invalid.
+
+// REQUIRES: vptr-validation
+
+#include <string.h>
+
+struct A {
+ virtual void f();
+};
+
+void A::f() {}
+
+struct B {
+ virtual void f();
+};
+
+void B::f() {}
+
+int main() {
+ // Create a fake vtable for A in writable memory and copy A's vtable into it.
+ void *fake_vtable[3];
+ A a;
+ void ***vtp = (void ***)&a;
+ memcpy(fake_vtable, *vtp - 2, sizeof(void *) * 3);
+ *vtp = fake_vtable + 2;
+
+ // A's vtable is invalid because it lives in writable memory.
+ // CHECK: invalid vptr
+ reinterpret_cast<B*>(&a)->f();
+}
diff --git a/test/ubsan/lit.common.cfg b/test/ubsan/lit.common.cfg
index 60b0b556c..2e0a0bc77 100644
--- a/test/ubsan/lit.common.cfg
+++ b/test/ubsan/lit.common.cfg
@@ -77,3 +77,6 @@ if config.host_os == 'Windows':
# because the test hangs or fails on one configuration and not the other.
if config.target_arch.startswith('arm') == False:
config.available_features.add('stable-runtime')
+
+if config.host_os == 'Linux':
+ config.available_features.add('vptr-validation')