Skip to content

[win/asan] Improve SharedReAlloc with HEAP_REALLOC_IN_PLACE_ONLY. #132558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

bernhardu
Copy link
Contributor

@bernhardu bernhardu commented Mar 22, 2025

This patch allows reallocations in place if the size is below or equal to the initial allocated size.

Copy link

github-actions bot commented Mar 22, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions h,cpp -- compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp compiler-rt/lib/asan/asan_allocator.cpp compiler-rt/lib/asan/asan_allocator.h compiler-rt/lib/asan/asan_malloc_win.cpp
View the diff from clang-format here.
diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp
index b4e7940bf..5f99c8ae8 100644
--- a/compiler-rt/lib/asan/asan_allocator.cpp
+++ b/compiler-rt/lib/asan/asan_allocator.cpp
@@ -527,10 +527,12 @@ struct Allocator {
 
   bool UpdateDeallocationStack(uptr addr, BufferedStackTrace *stack) {
     AsanChunk *m = GetAsanChunkByAddr(addr);
-    if (!m) return false;
+    if (!m)
+      return false;
     if (atomic_load(&m->chunk_state, memory_order_acquire) != CHUNK_ALLOCATED)
       return false;
-    if (m->Beg() != addr) return false;
+    if (m->Beg() != addr)
+      return false;
     AsanThread *t = GetCurrentThread();
     m->SetFreeContext(t ? t->tid() : kMainTid, StackDepotPut(*stack));
     return true;
@@ -952,8 +954,8 @@ uptr AsanChunkView::AllocTid() const {
 }
 
 uptr AsanChunkView::FreeTid() const {
-  //if (!IsQuarantined())
-  //  return kInvalidTid;
+  // if (!IsQuarantined())
+  //   return kInvalidTid;
   u32 tid = 0;
   u32 stack = 0;
   chunk_->GetFreeContext(tid, stack);
@@ -972,8 +974,8 @@ u32 AsanChunkView::GetAllocStackId() const {
 }
 
 u32 AsanChunkView::GetFreeStackId() const {
-  //if (!IsQuarantined())
-  //  return 0;
+  // if (!IsQuarantined())
+  //   return 0;
   u32 tid = 0;
   u32 stack = 0;
   chunk_->GetFreeContext(tid, stack);
@@ -1130,7 +1132,7 @@ void asan_mz_force_unlock() SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
   instance.ForceUnlock();
 }
 
-int asan_update_deallocation_context(void* addr, BufferedStackTrace *stack) {
+int asan_update_deallocation_context(void *addr, BufferedStackTrace *stack) {
   return instance.UpdateDeallocationStack((uptr)addr, stack);
 }
 
diff --git a/compiler-rt/lib/asan/asan_allocator.h b/compiler-rt/lib/asan/asan_allocator.h
index 7236543e3..f6cdb053f 100644
--- a/compiler-rt/lib/asan/asan_allocator.h
+++ b/compiler-rt/lib/asan/asan_allocator.h
@@ -292,7 +292,7 @@ uptr asan_mz_size(const void *ptr);
 void asan_mz_force_lock();
 void asan_mz_force_unlock();
 
-int asan_update_deallocation_context(void* addr, BufferedStackTrace *stack);
+int asan_update_deallocation_context(void *addr, BufferedStackTrace *stack);
 
 void PrintInternalAllocatorStats();
 void AsanSoftRssLimitExceededCallback(bool exceeded);
diff --git a/compiler-rt/lib/asan/asan_malloc_win.cpp b/compiler-rt/lib/asan/asan_malloc_win.cpp
index dbef1943a..3181bf213 100644
--- a/compiler-rt/lib/asan/asan_malloc_win.cpp
+++ b/compiler-rt/lib/asan/asan_malloc_win.cpp
@@ -14,13 +14,14 @@
 #include "sanitizer_common/sanitizer_allocator_interface.h"
 #include "sanitizer_common/sanitizer_platform.h"
 #if SANITIZER_WINDOWS
-#include "asan_allocator.h"
-#include "asan_interceptors.h"
-#include "asan_internal.h"
-#include "asan_poisoning.h"
-#include "asan_stack.h"
-#include "interception/interception.h"
-#include <stddef.h>
+#  include <stddef.h>
+
+#  include "asan_allocator.h"
+#  include "asan_interceptors.h"
+#  include "asan_internal.h"
+#  include "asan_poisoning.h"
+#  include "asan_stack.h"
+#  include "interception/interception.h"
 
 // Intentionally not including windows.h here, to avoid the risk of
 // pulling in conflicting declarations of these functions. (With mingw-w64,
@@ -324,7 +325,6 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc,
     }
 
     if (ownershipState == ASAN && !only_asan_supported_flags) {
-
       if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY) {
         size_t old_usable_size = asan_malloc_usable_size(lpMem, pc, bp);
         if (dwBytes == old_usable_size) {
@@ -335,10 +335,12 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc,
           return nullptr;
         } else {
           // poison just the "freed" part
-          uptr aligned_ptr = RoundUpTo((uptr)((char *)lpMem + dwBytes), ASAN_SHADOW_GRANULARITY);
+          uptr aligned_ptr = RoundUpTo((uptr)((char *)lpMem + dwBytes),
+                                       ASAN_SHADOW_GRANULARITY);
           uptr aligned_size = RoundUpTo(dwBytes, ASAN_SHADOW_GRANULARITY);
           PoisonShadow(aligned_ptr, aligned_size, kAsanHeapFreeMagic);
-          // unpoison the lower part, it could be poisoned by a previous realloc in place
+          // unpoison the lower part, it could be poisoned by a previous realloc
+          // in place
           __asan_unpoison_memory_region((char *)lpMem, dwBytes);
           __asan::asan_update_deallocation_context(lpMem, &stack);
           return lpMem;
diff --git a/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp b/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp
index 5773bca4b..1f1e3ccf6 100644
--- a/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp
+++ b/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp
@@ -41,7 +41,7 @@ int main() {
   buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 23),
 
   ret = RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_REALLOC_IN_PLACE_ONLY,
-                        buffer, 7);
+                              buffer, 7);
   if (!ret) {
     puts("returned nullptr");
   }
@@ -51,7 +51,7 @@ int main() {
   // CHECK: Okay 6
 
   ret = RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_REALLOC_IN_PLACE_ONLY,
-                        buffer, 15);
+                              buffer, 15);
   if (!ret) {
     puts("returned nullptr");
   }

@bernhardu
Copy link
Contributor Author

Hello, I wonder if such a patch would be acceptable?

Also I am not sure which configurations this should be tested before final submission?
Currently I have only tested it with a maybe less used llvm-mingw configuration.

I found also most heap(re)alloc or rtlallocateheap tests contain an UNSUPPORTED: asan-64-bits or REQUIRES: asan-32-bits. Is there a reason known why these are currently not enabled for 64-bits?

@bernhardu
Copy link
Contributor Author

Just corrected the clang-format.

Another point I forgot to mention, this currently creates an issue when running with ASAN_OPTIONS containing windows_hook_rtl_allocators=1", as it actually frees the memory which is attempted to be reallocated in place,
and shows a double-free when it gets regularly freed.

=================================================================
==mshtml_test.exe==1300==ERROR: AddressSanitizer: attempting double-free on 0x7f46b30bc800 in thread T-1:
    #0 0x6ffffe84b2e3 in RtlFreeHeap /home/runner/work/llvm-mingw/llvm-mingw/llvm-project/compiler-rt\lib/asan/asan_malloc_win.cpp:431:3
    #1 0x6ffff3d7323f in FreeContextBuffer .../wine/dlls/secur32/secur32.c:651:5
    #2 0x6ffffe0aede4 in netcon_secure_connect_setup .../wine/dlls/wininet/netconnection.c:484:13
    #3 0x6ffffe0ae854 in NETCON_secure_connect .../wine/dlls/wininet/netconnection.c:612:11
    #4 0x6ffffe08141d in HTTP_HttpSendRequestW .../wine/dlls/wininet/http.c:5100:23
...

0x7f46b30bc800 is located 0 bytes inside of 65536-byte region [0x7f46b30bc800,0x7f46b30cc800)
freed by thread T0 here:
    #0 0x6ffffe84ada2 in __asan::SharedReAlloc(void* (*)(void*, unsigned long, void*, unsigned long long), unsigned long long (*)(void*, unsigned long, void*), int (*)(void*, unsigned long, void*), void* (*)(void*, unsigned long, unsigned long long), void*, unsigned long, void*, unsigned long long) /home/runner/work/llvm-mingw/llvm-mingw/llvm-project/compiler-rt\lib/asan/asan_malloc_win.cpp:269:3
    #1 0x6ffffe84b174 in HeapReAlloc /home/runner/work/llvm-mingw/llvm-mingw/llvm-project/compiler-rt\lib/asan/asan_malloc_win.cpp:381:10
    #2 0x6ffff3d6f0c3 in establish_context .../wine/dlls/secur32/schannel.c:979:13
    #3 0x6ffff3d6e400 in schan_InitializeSecurityContextW .../wine/dlls/secur32/schannel.c:1043:12
    #4 0x6ffff3d7a9f6 in InitializeSecurityContextW .../wine/dlls/secur32/wrapper.c:249:19
    #5 0x6ffffe0aecae in netcon_secure_connect_setup .../wine/dlls/wininet/netconnection.c:464:14
    #6 0x6ffffe0ae854 in NETCON_secure_connect .../wine/dlls/wininet/netconnection.c:612:11
    #7 0x6ffffe08141d in HTTP_HttpSendRequestW .../wine/dlls/wininet/http.c:5100:23
...

CC: @zmodem, @mstorsjo, what do you think?

@bernhardu
Copy link
Contributor Author

To the question where this should be tested before submission, I wondered why the "Windows Premerge Check" did not run the tests. But then I found about .ci/compute-projects.sh function exclude-windows,
which contains compiler-rt) ;; # tests taking too long.

Is there any other way to submit a test to some buildbot or similar?

@mstorsjo
Copy link
Member

To the question where this should be tested before submission, I wondered why the "Windows Premerge Check" did not run the tests. But then I found about .ci/compute-projects.sh function exclude-windows, which contains compiler-rt) ;; # tests taking too long.

Is there any other way to submit a test to some buildbot or similar?

I'm not aware of any such setup unfortunately...

But I use a custom github actions setup for test running various things on the public github actions runners - see mstorsjo@gha-mingw-compiler-rt. I pushed a combination of that with this test branch, which should produce results at https://github.com/mstorsjo/llvm-project/actions/runs/14078778580.

It should probably be possible to do a similar setup with clang-cl as well; it's mainly a question if it can be built with a recent enough separate build of clang (so one can build just compiler-rt), or if it requires a full build of clang+compiler-rt at the same time (which takes a fair bit of time on the github actions runners).

@bernhardu
Copy link
Contributor Author

Thank you very much for the details and testing. Looks like the test succeeded here and here.
I will do some experiments.

@bernhardu bernhardu marked this pull request as ready for review April 10, 2025 16:15
@bernhardu
Copy link
Contributor Author

Hello everyone,
unfortunately getting a CI run with clang-cl without a complete build on the way took way longer than expected. And I assume my recipe is still not using the expected way, as I had to do a few modifications to get out of crt-hell.
However, this is a run that completed at last my proposed test rtlallocateheap_realloc_in_place.cpp for i686 and x86_64.

@llvmbot
Copy link
Member

llvmbot commented Apr 10, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: None (bernhardu)

Changes

This patch allows reallocations in place if the size is below or equal to the initial allocated size.

Currently it prints only a "use-after-poison" message, not a proper "heap-buffer-overflow" with a hint to a reallocation.


Full diff: https://github.com/llvm/llvm-project/pull/132558.diff

2 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_malloc_win.cpp (+15-2)
  • (added) compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp (+61)
diff --git a/compiler-rt/lib/asan/asan_malloc_win.cpp b/compiler-rt/lib/asan/asan_malloc_win.cpp
index 3278f07219876..4a8eb0acb174e 100644
--- a/compiler-rt/lib/asan/asan_malloc_win.cpp
+++ b/compiler-rt/lib/asan/asan_malloc_win.cpp
@@ -323,12 +323,24 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc,
     }
 
     if (ownershipState == ASAN && !only_asan_supported_flags) {
+      size_t old_usable_size = asan_malloc_usable_size(lpMem, pc, bp);
+      if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY) {
+        if (dwBytes >= 1 && dwBytes <= old_usable_size) {
+          if (dwBytes < old_usable_size) {
+            __asan_poison_memory_region((char *)lpMem + dwBytes,
+                                        old_usable_size - dwBytes);
+          }
+          __asan_unpoison_memory_region((char *)lpMem, dwBytes);
+          return lpMem;
+        } else {
+          return nullptr;
+        }
+      }
+
       // Conversion to unsupported flags allocation,
       // transfer this allocation back to the original allocator.
       void *replacement_alloc = allocFunc(hHeap, dwFlags, dwBytes);
-      size_t old_usable_size = 0;
       if (replacement_alloc) {
-        old_usable_size = asan_malloc_usable_size(lpMem, pc, bp);
         REAL(memcpy)(replacement_alloc, lpMem,
                      Min<size_t>(dwBytes, old_usable_size));
         asan_free(lpMem, &stack, FROM_MALLOC);
@@ -348,6 +360,7 @@ void *SharedReAlloc(ReAllocFunction reallocFunc, SizeFunction heapSizeFunc,
   }
   // asan_realloc will never reallocate in place, so for now this flag is
   // unsupported until we figure out a way to fake this.
+  // Small exception when shrinking or staying below the inital size, see above.
   if (dwFlags & HEAP_REALLOC_IN_PLACE_ONLY)
     return nullptr;
 
diff --git a/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp b/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp
new file mode 100644
index 0000000000000..ffe62b7c30723
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Windows/rtlallocateheap_realloc_in_place.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_cl_asan %Od %s %Fe%t %MD
+// RUN: %env_asan_opts=windows_hook_rtl_allocators=true:halt_on_error=false not %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <windows.h>
+
+using AllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, SIZE_T);
+using ReAllocateFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID, SIZE_T);
+using FreeFunctionPtr = PVOID(__stdcall *)(PVOID, ULONG, PVOID);
+
+int main() {
+  HMODULE NtDllHandle = GetModuleHandle("ntdll.dll");
+  if (!NtDllHandle) {
+    puts("Couldn't load ntdll??");
+    return -1;
+  }
+
+  auto RtlAllocateHeap_ptr =
+      (AllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlAllocateHeap");
+  if (RtlAllocateHeap_ptr == 0) {
+    puts("Couldn't RtlAllocateHeap");
+    return -1;
+  }
+
+  auto RtlReAllocateHeap_ptr =
+      (ReAllocateFunctionPtr)GetProcAddress(NtDllHandle, "RtlReAllocateHeap");
+  if (RtlReAllocateHeap_ptr == 0) {
+    puts("Couldn't find RtlReAllocateHeap");
+    return -1;
+  }
+
+  auto RtlFreeHeap_ptr =
+      (FreeFunctionPtr)GetProcAddress(NtDllHandle, "RtlFreeHeap");
+  if (RtlFreeHeap_ptr == 0) {
+    puts("Couldn't RtlFreeHeap");
+    return -1;
+  }
+
+  char *buffer;
+  buffer = (char *)RtlAllocateHeap_ptr(GetProcessHeap(), 0, 48),
+
+  RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_REALLOC_IN_PLACE_ONLY, buffer,
+                        16);
+  buffer[15] = 'a';
+  puts("Okay 15");
+  fflush(stdout);
+  // CHECK: Okay 15
+
+  RtlReAllocateHeap_ptr(GetProcessHeap(), HEAP_REALLOC_IN_PLACE_ONLY, buffer,
+                        32);
+  buffer[31] = 'a';
+  puts("Okay 31");
+  fflush(stdout);
+  // CHECK: Okay 31
+
+  buffer[32] = 'a';
+  // CHECK: AddressSanitizer: use-after-poison on address [[ADDR:0x[0-9a-f]+]]
+  // CHECK: WRITE of size 1 at [[ADDR]] thread T0
+
+  RtlFreeHeap_ptr(GetProcessHeap(), 0, buffer);
+}

@mstorsjo
Copy link
Member

Hello everyone, unfortunately getting a CI run with clang-cl without a complete build on the way took way longer than expected. And I assume my recipe is still not using the expected way, as I had to do a few modifications to get out of crt-hell.

Nice! (Btw I see that your actions jobs still are named llvm-mingw; it may be slightly less confusing if they'd be named something else.) :-)

@bernhardu
Copy link
Contributor Author

Hello everyone, any opinions on the patch?

@thurstond
Copy link
Contributor

Would this approach in general make bug detection worse? The existing behavior of realloc always returning a new pointer (with the old memory marked inaccessible) can catch erroneous code that assumes the realloc is in place (or worse, inconsistently uses both the old pointer and the return value of realloc).

Currently it prints only a "use-after-poison" message, not a proper "heap-buffer-overflow" with a hint to a reallocation.

This will be confusing to users and could lead them on a wild good chase, looking for bugs in poisoning.

@bernhardu
Copy link
Contributor Author

Thanks for having a look.

Currently it prints only a "use-after-poison" message, not a proper "heap-buffer-overflow" with a hint to a reallocation.

This will be confusing to users and could lead them on a wild good chase, looking for bugs in poisoning.

I will try to improve the message and try to avoid the bare "use-after-poison".

Would this approach in general make bug detection worse? The existing behavior of realloc always returning a new pointer (with the old memory marked inaccessible) can catch erroneous code that assumes the realloc is in place (or worse, inconsistently uses both the old pointer and the return value of realloc).

I am a little confused now - when I read the documentation to HeapReAlloc I understand the paragraph of HEAP_REALLOC_IN_PLACE_ONLY as it is not allowed to return a different pointer. And if resize cannot be done in place it has to fail e.g return NULL.
This patch should just modify behaviour when HEAP_REALLOC_IN_PLACE_ONLY is given.

@thurstond
Copy link
Contributor

Would this approach in general make bug detection worse? The existing behavior of realloc always returning a new pointer (with the old memory marked inaccessible) can catch erroneous code that assumes the realloc is in place (or worse, inconsistently uses both the old pointer and the return value of realloc).

I am a little confused now - when I read the documentation to HeapReAlloc I understand the paragraph of HEAP_REALLOC_IN_PLACE_ONLY as it is not allowed to return a different pointer. And if resize cannot be done in place it has to fail e.g return NULL. This patch should just modify behaviour when HEAP_REALLOC_IN_PLACE_ONLY is given.

Sorry, my bad. I wasn't familiar with the HEAP_REALLOC_IN_PLACE_ONLY API. Thanks for the pointer!

After reading it, I still wonder whether implementing this will reduce bug detection. For example, some code might incorrectly not expect NULL to be returned (even though the allocator is allowed to do so), or perhaps returning NULL will force the user code to call realloc without HEAP_REALLOC_IN_PLACE_ONLY (thereby allowing stronger use-after-free detection).

@bernhardu bernhardu marked this pull request as draft May 28, 2025 18:18
@bernhardu
Copy link
Contributor Author

Currently it prints only a "use-after-poison" message, not a proper "heap-buffer-overflow" with a hint to a reallocation.

This will be confusing to users and could lead them on a wild good chase, looking for bugs in poisoning.

I will try to improve the message and try to avoid the bare "use-after-poison".

I made now a bigger modification, which still tries to leave the chunk in an allocated state. But to show the stack of the partially free needed disabling a few checks IsQuarantined.

An example output of the test is here, would that be usable?
Okay 6
Okay 14
=================================================================
==572==ERROR: AddressSanitizer: heap-use-after-free on address 0x7eb83cbe001f at pc 0x000140001663 bp 0x7ffffe1ffdd0 sp 0x7ffffe1ffe18
WRITE of size 1 at 0x7eb83cbe001f thread T0
    #0 0x000140001662 in main ...\compiler-rt\test\asan\TestCases\Windows\rtlallocateheap_realloc_in_place.cpp:63:14
    #1 0x000140001338 in __tmainCRTStartup .../crt\crtexe.c:259:15
    #2 0x000140001395 in .l_start .../crt\crtexe.c:179:9
    #3 0x6fffffc45aa0 in BaseThreadInitThunk .../wine/dlls/kernel32/thread.c:61:24
    #4 0x6fffffdcc896 in RtlUserThreadStart (C:\windows\system32\ntdll.dll+0x17004c896)

0x7eb83cbe001f is located 15 bytes inside of 23-byte region [0x7eb83cbe0010,0x7eb83cbe0027)
partially freed by thread T0 here:
    #0 0x6ffffba6c316 in __asan::SharedReAlloc(void* (*)(void*, unsigned long, void*, unsigned long long), unsigned long long (*)(void*, unsigned long, void*), int (*)(void*, unsigned long, void*), void* (*)(void*, unsigned long, unsigned long long), void*, unsigned long, void*, unsigned long long) C:/llvm-mingw/llvm-mingw/llvm-project/compiler-rt/lib/asan\asan_malloc_win.cpp:270:3
    #1 0x6ffffba6c76a in HeapReAlloc C:/llvm-mingw/llvm-mingw/llvm-project/compiler-rt/lib/asan\asan_malloc_win.cpp:404:10
    #2 0x0001400015b5 in main ...\compiler-rt\test\asan\TestCases\Windows\rtlallocateheap_realloc_in_place.cpp:53:9
    #3 0x000140001338 in __tmainCRTStartup .../crt\crtexe.c:259:15
    #4 0x000140001395 in .l_start .../crt\crtexe.c:179:9
    #5 0x6fffffc45aa0 in BaseThreadInitThunk .../wine/dlls/kernel32/thread.c:61:24
    #6 0x6fffffdcc896 in RtlUserThreadStart (C:\windows\system32\ntdll.dll+0x17004c896)

previously allocated by thread T0 here:
    #0 0x6ffffba6bff8 in HeapAlloc C:/llvm-mingw/llvm-mingw/llvm-project/compiler-rt/lib/asan\asan_malloc_win.cpp:231:3
    #1 0x0001400014f0 in main ...\compiler-rt\test\asan\TestCases\Windows\rtlallocateheap_realloc_in_place.cpp:41:20
    #2 0x000140001338 in __tmainCRTStartup .../crt\crtexe.c:259:15
    #3 0x000140001395 in .l_start .../crt\crtexe.c:179:9
    #4 0x6fffffc45aa0 in BaseThreadInitThunk .../wine/dlls/kernel32/thread.c:61:24
    #5 0x6fffffdcc896 in RtlUserThreadStart (C:\windows\system32\ntdll.dll+0x17004c896)

SUMMARY: AddressSanitizer: heap-use-after-free ...\compiler-rt\test\asan\TestCases\Windows\rtlallocateheap_realloc_in_place.cpp:63:14 in main
Shadow bytes around the buggy address:
  0x7eb83cbdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7eb83cbdfe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7eb83cbdfe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7eb83cbdff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7eb83cbdff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7eb83cbe0000: fa fa 00[07]fd fd fa fa fa fa fa fa fa fa fa fa
  0x7eb83cbe0080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7eb83cbe0100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7eb83cbe0180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7eb83cbe0200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7eb83cbe0280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==572==ABORTING

@thurstond
Copy link
Contributor

I made now a bigger modification, which still tries to leave the chunk in an allocated state. But to show the stack of the partially free needed disabling a few checks IsQuarantined.

I'm kind of nervous that this patch now involves changes to the core ASan allocator. Indeed, it breaks Linux tests: https://buildkite.com/llvm-project/github-pull-requests/builds/184040#0197181e-8ad6-4377-bf55-de70d4efb035

An example output of the test is here, would that be usable?

"partially freed" (as opposed to "freed") is an unnecessary and potentially confusing distinction IMO. The stack trace will already show it is from realloc.

This patch allows reallocations in place if the size is
below or equal to the initial allocated size.
@bernhardu
Copy link
Contributor Author

I'm kind of nervous that this patch now involves changes to the core ASan allocator. Indeed, it breaks Linux tests: ...

"partially freed" (as opposed to "freed") is an unnecessary and potentially confusing distinction IMO. The stack trace will already show it is from realloc.

I added this "partially" because the lower part of the chunk is still valid, it is just the upper part which got "freed".
In the current push I removed this "partially", but there is still a change to asan_allocator to be able to store and retrieve the "freed" stack, while the chunk is not really/completely freed.
And this seems to fail in linux as there is a freed stack returned because I disabled the check, while it should not.

Thanks for looking into it, I fear a chunk which is allocated and "freed" is not fitting the asan_allocator, maybe splitting the chunk into an allocated and into a freed one would be possible ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants