[PATCH] D12790: ubsan: Implement memory permission validation for vtables.

Peter Collingbourne via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 18:21:54 PDT 2015


pcc created this revision.
pcc added reviewers: kcc, krasin, rsmith.
pcc added a subscriber: llvm-commits.

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.

http://reviews.llvm.org/D12790

Files:
  lib/ubsan/ubsan_type_hash_itanium.cc
  test/ubsan/TestCases/Linux/TypeCheck/vptr-bad-perms.cpp
  test/ubsan/TestCases/Linux/lit.local.cfg

Index: test/ubsan/TestCases/Linux/lit.local.cfg
===================================================================
--- /dev/null
+++ test/ubsan/TestCases/Linux/lit.local.cfg
@@ -0,0 +1,9 @@
+def getRoot(config):
+  if not config.parent:
+    return config
+  return getRoot(config.parent)
+
+root = getRoot(config)
+
+if root.host_os not in ['Linux']:
+  config.unsupported = True
Index: test/ubsan/TestCases/Linux/TypeCheck/vptr-bad-perms.cpp
===================================================================
--- /dev/null
+++ test/ubsan/TestCases/Linux/TypeCheck/vptr-bad-perms.cpp
@@ -0,0 +1,29 @@
+// RUN: %clangxx -frtti -fsanitize=vptr -fno-sanitize-recover=vptr -g %s -O3 -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#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();
+}
Index: lib/ubsan/ubsan_type_hash_itanium.cc
===================================================================
--- lib/ubsan/ubsan_type_hash_itanium.cc
+++ 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
@@ -192,6 +193,33 @@
   std::type_info *TypeInfo;
 };
 VtablePrefix *getVtablePrefix(void *Vtable) {
+  if (!IsAccessibleMemoryRange((uptr)Vtable, sizeof(void *)))
+    return 0;
+
+#if SANITIZER_LINUX
+  // 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 0;
+#endif
+
   VtablePrefix *Vptr = reinterpret_cast<VtablePrefix*>(Vtable);
   if (!Vptr)
     return 0;


-------------- next part --------------
A non-text attachment was scrubbed...
Name: D12790.34520.patch
Type: text/x-patch
Size: 3149 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20150911/21e22f7c/attachment.bin>


More information about the llvm-commits mailing list