Skip to content
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

[ASan] Honor allocator_may_return_null when set through user-function and fix large alloc edge case #117929

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

davidmrdavid
Copy link

@davidmrdavid davidmrdavid commented Nov 27, 2024

Related: #117925

About this PR:
This PR performs 3 small but related fixes for ASan users on Windows:

  1. It ensures that the allocator_may_return_null flag is honored when set through the user function __asan_default_options. For more details, please see: [Asan] ASan flags set through a user-defined __asan_default_options function may not be honored in Windows #117925
  2. It adds a missing AllocatorMayReturnNull() check inside InternalAlloc that's needed to avoid error'ing out when the allocator correctly returns null when allocator_may_return_null is set.
  3. In sanitizer_win's ReturnNullptrOnOOMOrDie, it allows returning null when the last error is set to ERROR_INVALID_PARAMETER which may be set by VirtualAlloc on WIndows when attempting to allocate exceedingly large memory.

I've added test cases that should cover these new behaviors. Happy to take on any feedback as well. Thank you :-)

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Nov 27, 2024

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

Author: David Justo (davidmrdavid)

Changes

**Related: ** #117925

About this PR:
This PR performs 3 small but related fixes for ASan users on Windows:

  1. It ensures that the allocator_may_return_null flag is honored when set through the user function __asan_default_options. For more details, please see: #117925
  2. It adds a missing AllocatorMayReturnNull() check inside InternalAlloc that's needed to avoid error'ing out when the allocator correctly returns null when allocator_may_return_null is set.
  3. In sanitizer_win's ReturnNullptrOnOOMOrDie, it allows returning null when the last error is set to ERROR_INVALID_PARAMETER which may be set by VirtualAlloc on WIndows when attempting to allocate exceedingly large memory.

I've added test cases that should cover these new behaviors. Happy to take on any feedback as well. Thank you :-)


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

5 Files Affected:

  • (modified) compiler-rt/lib/asan/asan_flags.cpp (+7)
  • (modified) compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp (+5-1)
  • (modified) compiler-rt/lib/sanitizer_common/sanitizer_win.cpp (+17-1)
  • (added) compiler-rt/test/asan/TestCases/Windows/allocator_may_return_null_set_via_user_function.cpp (+26)
  • (added) compiler-rt/test/asan/TestCases/allocator_may_return_null_limits.cpp (+22)
diff --git a/compiler-rt/lib/asan/asan_flags.cpp b/compiler-rt/lib/asan/asan_flags.cpp
index 56deb1b0d082b8..1499c63fd19014 100644
--- a/compiler-rt/lib/asan/asan_flags.cpp
+++ b/compiler-rt/lib/asan/asan_flags.cpp
@@ -240,6 +240,13 @@ void InitializeFlags() {
 
         DisplayHelpMessages(&asan_parser);
         ProcessFlags();
+
+        // TODO: Update other globals and data structures that may change after
+        // initialization due to these flags potentially changing.
+        // These flags can be identified by looking at the work done in
+        // `AsanInitInternal` inside of `asan_rtl.cpp`.
+        // See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for details.
+        SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
       });
 
 #  if CAN_SANITIZE_UB
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
index 9d899371c2dd90..b8c772d7df6c68 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
@@ -85,8 +85,12 @@ static void NORETURN ReportInternalAllocatorOutOfMemory(uptr requested_size) {
 
 void *InternalAlloc(uptr size, InternalAllocatorCache *cache, uptr alignment) {
   void *p = RawInternalAlloc(size, cache, alignment);
-  if (UNLIKELY(!p))
+  if (UNLIKELY(!p)) {
+    if (AllocatorMayReturnNull()){
+      return nullptr;
+    }
     ReportInternalAllocatorOutOfMemory(size);
+  }
   return p;
 }
 
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
index ea513d5f263fe2..045a9a9d7b0b2d 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
@@ -164,7 +164,23 @@ void UnmapOrDie(void *addr, uptr size, bool raw_report) {
 static void *ReturnNullptrOnOOMOrDie(uptr size, const char *mem_type,
                                      const char *mmap_type) {
   error_t last_error = GetLastError();
-  if (last_error == ERROR_NOT_ENOUGH_MEMORY)
+
+  // Assumption: VirtualAlloc is the last system call that was invoked before
+  //   this method.
+  // VirtualAlloc emits one of 2 error codes when running out of memory
+  // 1. ERROR_NOT_ENOUGH_MEMORY:
+  //  There's not enough memory to execute the command
+  // 2. ERROR_INVALID_PARAMETER:
+  //  VirtualAlloc will return this if the request would allocate memory at an
+  //  address exceeding or being very close to the maximum application address
+  //  (the `lpMaximumApplicationAddress` field within the `SystemInfo` struct).
+  //  This does not seem to be officially documented, but is corroborated here:
+  //  https://stackoverflow.com/questions/45833674/why-does-virtualalloc-fail-for-lpaddress-greater-than-0x6ffffffffff
+
+  // Note - It's possible that 'ERROR_COMMITMENT_LIMIT' needs to be handled here as well.
+  // It is currently not handled due to the lack of a reproducer that induces the error code.
+  if (last_error == ERROR_NOT_ENOUGH_MEMORY ||
+      last_error == ERROR_INVALID_PARAMETER)
     return nullptr;
   ReportMmapFailureAndDie(size, mem_type, mmap_type, last_error);
 }
diff --git a/compiler-rt/test/asan/TestCases/Windows/allocator_may_return_null_set_via_user_function.cpp b/compiler-rt/test/asan/TestCases/Windows/allocator_may_return_null_set_via_user_function.cpp
new file mode 100644
index 00000000000000..23af2224ec4f08
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Windows/allocator_may_return_null_set_via_user_function.cpp
@@ -0,0 +1,26 @@
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %run %t 2>&1
+// CHECK: Success
+
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+
+// On Windows, flags configured through the user-defined function `__asan_default_options`
+// are suspected to not always be honored according to this GH issue:
+// https://github.com/llvm/llvm-project/issues/117925
+// This issue is resolved for the `allocator_may_return_null` flag, but not for all others.
+// This test ensures we do not regress on `allocator_may_return_null` specifically.
+extern "C" __declspec(dllexport) extern const char *__asan_default_options() {
+  return "allocator_may_return_null=1";
+}
+
+int main() {
+  // Attempt to allocate an excessive amount of memory, which should
+  // terminate the program unless `allocator_may_return_null` is set.
+  size_t max = std::numeric_limits<size_t>::max();
+
+  free(malloc(max));
+  printf("Success");
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/allocator_may_return_null_limits.cpp b/compiler-rt/test/asan/TestCases/allocator_may_return_null_limits.cpp
new file mode 100644
index 00000000000000..515ac2643e161a
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/allocator_may_return_null_limits.cpp
@@ -0,0 +1,22 @@
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK1
+// RUN: %env_asan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK2
+
+// CHECK1: exceeds maximum supported size
+// CHECK1: ABORT
+
+// CHECK2: Success
+
+#include <cstdint>
+#include <cstdlib>
+#include <limits>
+
+int main() {
+  // Attempt to allocate an excessive amount of memory, which should
+  // terminate the program unless `allocator_may_return_null` is set.
+  size_t max = std::numeric_limits<size_t>::max();
+
+  free(malloc(max));
+  printf("Success");
+  return 0;
+}

@vitalybuka vitalybuka self-requested a review November 27, 2024 21:35
Copy link

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

You can test this locally with the following command:
git-clang-format --diff 2f55de4e317ee93cdca839558acf8be2b5ac2b46 873cc218108826e4dd4413dccd5c5f6cb0757c84 --extensions cpp -- compiler-rt/test/asan/TestCases/Windows/allocator_may_return_null_set_via_user_function.cpp compiler-rt/test/asan/TestCases/allocator_may_return_null_limits.cpp compiler-rt/lib/asan/asan_flags.cpp compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
View the diff from clang-format here.
diff --git a/compiler-rt/lib/asan/asan_flags.cpp b/compiler-rt/lib/asan/asan_flags.cpp
index 1499c63fd1..bb40d61c47 100644
--- a/compiler-rt/lib/asan/asan_flags.cpp
+++ b/compiler-rt/lib/asan/asan_flags.cpp
@@ -245,7 +245,8 @@ void InitializeFlags() {
         // initialization due to these flags potentially changing.
         // These flags can be identified by looking at the work done in
         // `AsanInitInternal` inside of `asan_rtl.cpp`.
-        // See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for details.
+        // See GH issue 'https://github.com/llvm/llvm-project/issues/117925' for
+        // details.
         SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
       });
 
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
index b8c772d7df..cef175c6f6 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_allocator.cpp
@@ -86,7 +86,7 @@ static void NORETURN ReportInternalAllocatorOutOfMemory(uptr requested_size) {
 void *InternalAlloc(uptr size, InternalAllocatorCache *cache, uptr alignment) {
   void *p = RawInternalAlloc(size, cache, alignment);
   if (UNLIKELY(!p)) {
-    if (AllocatorMayReturnNull()){
+    if (AllocatorMayReturnNull()) {
       return nullptr;
     }
     ReportInternalAllocatorOutOfMemory(size);
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
index 045a9a9d7b..1eef16fbde 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
@@ -177,8 +177,9 @@ static void *ReturnNullptrOnOOMOrDie(uptr size, const char *mem_type,
   //  This does not seem to be officially documented, but is corroborated here:
   //  https://stackoverflow.com/questions/45833674/why-does-virtualalloc-fail-for-lpaddress-greater-than-0x6ffffffffff
 
-  // Note - It's possible that 'ERROR_COMMITMENT_LIMIT' needs to be handled here as well.
-  // It is currently not handled due to the lack of a reproducer that induces the error code.
+  // Note - It's possible that 'ERROR_COMMITMENT_LIMIT' needs to be handled here
+  // as well. It is currently not handled due to the lack of a reproducer that
+  // induces the error code.
   if (last_error == ERROR_NOT_ENOUGH_MEMORY ||
       last_error == ERROR_INVALID_PARAMETER)
     return nullptr;

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.

2 participants