[asan] Re-poison all redzones on activation.
authorEvgeniy Stepanov <eugeni.stepanov@gmail.com>
Tue, 13 Sep 2016 18:38:40 +0000 (18:38 +0000)
committerEvgeniy Stepanov <eugeni.stepanov@gmail.com>
Tue, 13 Sep 2016 18:38:40 +0000 (18:38 +0000)
When running with start_deactivated=1 in ASAN_OPTIONS, heap redzones
are not poisoned until the first instrumented module is loaded. This
can cause false negatives even on memory allocated after activation,
because redzones are normally poisoned only once when a new allocator
region is mapped.

This change attempts to fix it by iterating over all existing
allocator chunks and poisoning their redzones.

llvm-svn: 281364

compiler-rt/lib/asan/asan_allocator.cc
compiler-rt/test/asan/TestCases/Posix/start-deactivated.cc

index aa08969..7259060 100644 (file)
@@ -266,9 +266,43 @@ struct Allocator {
     SharedInitCode(options);
   }
 
+  void RePoisonChunk(uptr chunk) {
+    // This could a user-facing chunk (with redzones), or some internal
+    // housekeeping chunk, like TransferBatch. Start by assuming the former.
+    AsanChunk *ac = GetAsanChunk((void *)chunk);
+    uptr allocated_size = allocator.GetActuallyAllocatedSize((void *)ac);
+    uptr beg = ac->Beg();
+    uptr end = ac->Beg() + ac->UsedSize(true);
+    uptr chunk_end = chunk + allocated_size;
+    if (chunk < beg && beg < end && end <= chunk_end) {
+      // Looks like a valid AsanChunk. Or maybe not. Be conservative and only
+      // poison the redzones.
+      PoisonShadow(chunk, beg - chunk, kAsanHeapLeftRedzoneMagic);
+      uptr end_aligned_down = RoundDownTo(end, SHADOW_GRANULARITY);
+      FastPoisonShadowPartialRightRedzone(
+          end_aligned_down, end - end_aligned_down,
+          chunk_end - end_aligned_down, kAsanHeapLeftRedzoneMagic);
+    } else {
+      // This can not be an AsanChunk. Poison everything. It may be reused as
+      // AsanChunk later.
+      PoisonShadow(chunk, allocated_size, kAsanHeapLeftRedzoneMagic);
+    }
+  }
+
   void ReInitialize(const AllocatorOptions &options) {
     allocator.SetMayReturnNull(options.may_return_null);
     SharedInitCode(options);
+
+    // Poison all existing allocation's redzones.
+    if (CanPoisonMemory()) {
+      allocator.ForceLock();
+      allocator.ForEachChunk(
+          [](uptr chunk, void *alloc) {
+            ((Allocator *)alloc)->RePoisonChunk(chunk);
+          },
+          this);
+      allocator.ForceUnlock();
+    }
   }
 
   void GetOptions(AllocatorOptions *options) const {
index 187ee5e..9691404 100644 (file)
@@ -2,8 +2,8 @@
 // Main executable is uninstrumented, but linked to ASan runtime. The shared
 // library is instrumented. Memory errors before dlopen are not detected.
 
-// RUN: %clangxx_asan -O0 -DSHARED_LIB %s -fPIC -shared -o %t-so.so
-// RUN: %clangxx -O0 %s -c -o %t.o
+// RUN: %clangxx_asan -O0 -DSHARED_LIB %s -std=c++11 -fPIC -shared -o %t-so.so
+// RUN: %clangxx -O0 %s -std=c++11 -c -o %t.o
 // RUN: %clangxx_asan -O0 %t.o %libdl -o %t
 // RUN: %env_asan_opts=start_deactivated=1,allocator_may_return_null=0 \
 // RUN:   ASAN_ACTIVATION_OPTIONS=allocator_may_return_null=1 not %run %t 2>&1 | FileCheck %s
 
 #include "sanitizer/asan_interface.h"
 
-void test_malloc_shadow() {
-  char *p = (char *)malloc(100);
-  char *q = (char *)__asan_region_is_poisoned(p + 95, 8);
-  fprintf(stderr, "=%zd=\n", q ? q - (p + 95) : -1);
-  free(p);
+constexpr unsigned nPtrs = 200;
+char *ptrs[nPtrs];
+
+void test_malloc_shadow(char *p, size_t sz, bool expect_redzones) {
+  assert((char *)__asan_region_is_poisoned(p - 1, sz + 1) ==
+         (expect_redzones ? p - 1 : nullptr));
+  assert((char *)__asan_region_is_poisoned(p, sz) == nullptr);
+  assert((char *)__asan_region_is_poisoned(p, sz + 1) ==
+         (expect_redzones ? p + sz : nullptr));
 }
 
 typedef void (*Fn)();
 
 int main(int argc, char *argv[]) {
-  test_malloc_shadow();
-  // CHECK: =-1=
+  // Before activation: no redzones.
+  for (size_t sz = 1; sz < nPtrs; ++sz) {
+    ptrs[sz] = (char *)malloc(sz);
+    test_malloc_shadow(ptrs[sz], sz, false);
+  }
 
   std::string path = std::string(argv[0]) + "-so.so";
   void *dso = dlopen(path.c_str(), RTLD_NOW);
@@ -52,9 +59,6 @@ int main(int argc, char *argv[]) {
     return 1;
   }
 
-  test_malloc_shadow();
-  // CHECK: =5=
-
   // After this line ASan is activated and starts detecting errors.
   void *fn = dlsym(dso, "do_another_bad_thing");
   if (!fn) {
@@ -62,6 +66,19 @@ int main(int argc, char *argv[]) {
     return 1;
   }
 
+  // After activation: redzones.
+  {
+    char *p = (char *)malloc(100);
+    test_malloc_shadow(p, 100, true);
+    free(p);
+  }
+
+  // Pre-existing allocations got redzones, too.
+  for (size_t sz = 1; sz < nPtrs; ++sz) {
+    test_malloc_shadow(ptrs[sz], sz, true);
+    free(ptrs[sz]);
+  }
+
   // Test that ASAN_ACTIVATION_OPTIONS=allocator_may_return_null=1 has effect.
   void *p = malloc((unsigned long)-2);
   assert(!p);