Skip to content

Conversation

bernhardu
Copy link
Contributor

@bernhardu bernhardu commented Mar 22, 2025

Currently with HEAP_REALLOC_IN_PLACE_ONLY a new allocation
gets returned with the content copied from the original pointer,
which gets freed.

But applications may rely on HEAP_REALLOC_IN_PLACE_ONLY returning
the same pointer as they give as input to e.g. RtlReAllocateHeap.
If e.g. growing is not possible it fails without
modifying the input pointer.

Downside of this patch is, it won't detect accesses to the area
getting "free" by a shrinking reallocation.

Copy link

github-actions bot commented Mar 22, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@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.

@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 ...

@bernhardu bernhardu force-pushed the HeapReAlloc_1 branch 2 times, most recently from 43a7fe9 to 882cf04 Compare July 26, 2025 14:38
@bernhardu bernhardu marked this pull request as ready for review July 26, 2025 14:52
@bernhardu
Copy link
Contributor Author

Sorry for the delay.
This last push takes a step back and does no longer touch the core allocator.

The main motivation for this pull request is to avoid to return a different pointer while IN_PLACE is requested.
This achieves this patch by simply returning the same pointer or null on failure, but in both cases it does not
modifiy the original allocation.
Therefore applications which rely on the address of the allocation not changing no longer get a heap-use-after-free.
But it won't therefore not catch accesses to the "freed" part of a shrunk allocation.

Copy link
Contributor

@thurstond thurstond left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier comment stands:

"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)."

Would you consider placing the new behavior configurable via an ASAN_OPTION?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for doing the check here, instead of on line 367 (which would become redundant)?

Copy link
Contributor Author

@bernhardu bernhardu Jul 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because line 367 is not reached. Instead the function is left via reallocFunc(hHeap, dwFlags, lpMem, dwBytes); in line 321.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, correction, not line 321, the function is left in line 352, so line 367 is not reached.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see. I had misunderstood the code.

@bernhardu
Copy link
Contributor Author

@thurstond, sorry for not responding to this earlier.

My earlier comment stands:
_"I still wonder whether implementing this will reduce bug detection.

In the previous version shrinking was possible, but either accesses wrote an "use-after-poison" message, or a later version took changes to the core asan allocator. I think these two approaches were not a reduction in bug detection.
The current version may reduce bug detection, as it ignores shrink requests, so accesses to the part of an allocation, which the appilcation tried to free, get not detected, if the application also ignores the returned NULL.

For example, some code might incorrectly not expect NULL to be returned (even though the allocator is allowed to do so),

From my point of view an application using HEAP_REALLOC_IN_PLACE_ONLY and receiving NULL is allowed to access the memory via the old pointer, as the allocation should stay unmodified in that case (see documentation, near HEAP_REALLOC_IN_PLACE_ONLY.
If it tries to access the memory via the returned NULL, and therefore crashing, I assume this points out this is an application bug, which would be a good thing to make visible?

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)."_

This is the intention of the last version to force the application into using using the failure path. I think the application has to be always prepared for receiving NULL when growing the allocation.
When the application does not know how many bytes it exactly needs and reserved more memory in the first place and wants to free the unneeded part, the application may just not care and just ignore if the shrinking failed or not.

Would you consider placing the new behavior configurable via an ASAN_OPTION?

I did not yet consider making this configurable because in my opinion returning a differenct location with HEAP_REALLOC_IN_PLACE_ONLY is simply a bug.

But if this is the desired way to proceed I would happily try to add this in the next push?
Thanks for looking at this pull request.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see. I had misunderstood the code.

Comment on lines +327 to +339
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this were all replaced with return nullptr;'? Would that provide stronger protection, while still staying spec-compliant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, it took me a while to run some tests.
And I found following "real world" location as an example [1] in the Wine tree to fail when always returning nullptr with HEAP_REALLOC_IN_PLACE_ONLY.

So in the end it boils down to the question if an application is allowed to rely on at least shrinking to succeed?
What do you think?

[1] https://github.com/wine-mirror/wine/blob/288a40d05c8cddf62d0b12524a90d2d4f5ce114d/dlls/kernelbase/locale.c#L5493-L5517

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating! Given this, I concur with the approach of your patch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@thurstond thurstond left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch! :-)

@bernhardu
Copy link
Contributor Author

@thurstond, sorry again for the delay and thanks for your patience.
Do you still like this to be configurable via ASAN_OPTIONS?
If yes, should the current behaviour stay the default (for now)?
With some name like "realloc_in_place_always_same_address" or "realloc_in_place_allow_other_address"?

@thurstond
Copy link
Contributor

@thurstond, sorry again for the delay and thanks for your patience. Do you still like this to be configurable via ASAN_OPTIONS? If yes, should the current behaviour stay the default (for now)? With some name like "realloc_in_place_always_same_address" or "realloc_in_place_allow_other_address"?

Since the new behavior is supposed to be more correct, we can try it without adding a configuration option.

If we discover that users in the wild need the old behavior for compatibility, we can revert and/or add a configuration option.

Is the patch ready to merge?

@bernhardu
Copy link
Contributor Author

Is the patch ready to merge?

I planned to do another push with the style thing and another check if the test is still really testing what I want to test, I hope that is ok.

@thurstond
Copy link
Contributor

Is the patch ready to merge?

I planned to do another push with the style thing and another check if the test is still really testing what I want to test, I hope that is ok.

Sure, no rush!

Currently with HEAP_REALLOC_IN_PLACE_ONLY a new allocation
gets returned with the content copied from the original pointer,
which gets freed.

But applications may rely on HEAP_REALLOC_IN_PLACE_ONLY returning
the same pointer as they give as input to e.g. RtlReAllocateHeap.
If e.g. growing is not possible it fails without
modifying the input pointer.

Downside of this patch is, it won't detect accesses to the area
getting "free" by a shrinking reallocation.
@bernhardu
Copy link
Contributor Author

Is the patch ready to merge?

I planned to do another push with the style thing and another check if the test is still really testing what I want to test, I hope that is ok.

Sure, no rush!

I pushed another version which just removes the superfluous curly brackets.
This are some private CI runs for i386 and x86_64.
So I think it is ready for merge, thanks for your time.

@thurstond thurstond merged commit d349daa into llvm:main Sep 3, 2025
9 checks passed
@thurstond
Copy link
Contributor

@bernhardu Thanks for the patch! Merged.

ckoparkar added a commit to ckoparkar/llvm-project that referenced this pull request Sep 4, 2025
* main: (1483 commits)
  [clang] fix error recovery for invalid nested name specifiers (llvm#156772)
  Revert "[lldb] Add count for errors of DWO files in statistics and combine DWO file count functions" (llvm#156777)
  AMDGPU: Add agpr variants of multi-data DS instructions (llvm#156420)
  [libc][NFC] disable localtime on aarch64/baremetal (llvm#156776)
  [win/asan] Improve SharedReAlloc with HEAP_REALLOC_IN_PLACE_ONLY. (llvm#132558)
  [LLDB] Make internal shell the default for running LLDB lit tests. (llvm#156729)
  [lldb][debugserver] Max response size for qSpeedTest (llvm#156099)
  [AMDGPU] Define 1024 VGPRs on gfx1250 (llvm#156765)
  [flang] Check for BIND(C) name conflicts with alternate entries (llvm#156563)
  [RISCV] Add exhausted_gprs_fprs test to calling-conv-half.ll. NFC (llvm#156586)
  [NFC] Remove trailing whitespaces from `clang/include/clang/Basic/AttrDocs.td`
  [lldb] Mark scripted frames as synthetic instead of artificial (llvm#153117)
  [docs] Refine some of the wording in the quality developer policy (llvm#156555)
  [MLIR] Apply clang-tidy fixes for readability-identifier-naming in TransformOps.cpp (NFC)
  [MLIR] Add LDBG() tracing to VectorTransferOpTransforms.cpp (NFC)
  [NFC] Apply clang-format to PPCInstrFutureMMA.td (llvm#156749)
  [libc] implement template functions for localtime (llvm#110363)
  [llvm-objcopy][COFF] Update .symidx values after stripping (llvm#153322)
  Add documentation on debugging LLVM.
  [lldb] Add count for errors of DWO files in statistics and combine DWO file count functions (llvm#155023)
  ...
@bernhardu bernhardu deleted the HeapReAlloc_1 branch September 4, 2025 11:44
@bernhardu
Copy link
Contributor Author

Thanks for merging.

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