Skip to content

[Sanitizers][Apple] Fix logic bugs that break RestrictMemoryToMaxAddress #124712

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

Merged

Conversation

thetruestblue
Copy link
Contributor

@thetruestblue thetruestblue commented Jan 28, 2025

There are two logic bugs breaking RestrictMemoryToMaxAddress.

  1. adding left_padding within MapDynamicShadow.
  • RoundUpTo((uptr)free_begin + left_padding, alignment) already adjusts for left padding. Adding this additionally within MapDynamicShadow causes us to allocate a page larger than necessary.
  • This incorrect calculation also means RestrictMemoryToMaxAddress will never find a big enough gap.
  1. There is also an issue with the expectation of hitting KERN_INVALID_ADDRESS when we are beyond the addressable regions.
  • For most embedded scenarios, we exceed vm_max_address without getting KREN_INVALID_ADDRESS so we setting max_occupied_address to a memory region the process doesn't have access to, beyond the max address, and that space is never marked as available so we never find a valid gap in those regions.
  • At some point previous it seems the assumption was once we were beyond the Max address we could expect KREN_INVALID_ADDRESS, which is no longer true up through the extended space not given to most processes.
  • Because of this, the check if (new_max_vm < max_occupied_addr) will always fail and we will never restrict the address on smaller devices.
  • Additionally because of the extra page added by adding left_padding, and how we only minimally restrict the vm, there's a chance we restrict the vm only enough for the correctly calculated size of shadow. In these cases, restricting the vm max address and will always fail due to the extra page added to space size.

credit to @delcypher for the left_padding diagnosis, remembered his old radar and PR when investigating this. https://reviews.llvm.org/D85389

rdar://66603866

@llvmbot
Copy link
Member

llvmbot commented Jan 28, 2025

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

Author: None (thetruestblue)

Changes

There are two logic bugs breaking RestrictMemoryToMaxAddress -- adding left_padding within MapDynamicShadow.

There is also an issue with the expectation of hitting KERN_INVALID_ADDRESS when we are beyond the addressable regions.

For most embedded scenarios, we exceed vm_max_address and setting max_occupied address to a memory region the process doesn't have access to.

Because of this, our check if (new_max_vm < max_occupied_addr) { will always fail and we will never restrict the address on smaller devices.

rdar://66603866


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

1 Files Affected:

  • (modified) compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp (+4-3)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp
index d15f30c61b5863..ade2ed2da6e4aa 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp
@@ -1203,7 +1203,7 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale,
   const uptr left_padding =
       Max<uptr>(granularity, 1ULL << min_shadow_base_alignment);
 
-  uptr space_size = shadow_size_bytes + left_padding;
+  uptr space_size = shadow_size_bytes;
 
   uptr largest_gap_found = 0;
   uptr max_occupied_addr = 0;
@@ -1229,7 +1229,7 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale,
     }
     RestrictMemoryToMaxAddress(new_max_vm);
     high_mem_end = new_max_vm - 1;
-    space_size = (high_mem_end >> shadow_scale) + left_padding;
+    space_size = (high_mem_end >> shadow_scale);
     VReport(2, "FindDynamicShadowStart, space_size = %p\n", (void *)space_size);
     shadow_start = FindAvailableMemoryRange(space_size, alignment, granularity,
                                             nullptr, nullptr);
@@ -1272,10 +1272,11 @@ uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding,
     mach_msg_type_number_t count = kRegionInfoSize;
     kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth,
                                 (vm_region_info_t)&vminfo, &count);
-    if (kr == KERN_INVALID_ADDRESS) {
+    if (kr == KERN_INVALID_ADDRESS || address > GetMaxVirtualAddress()) {
       // No more regions beyond "address", consider the gap at the end of VM.
       address = max_vm_address;
       vmsize = 0;
+      kr = -1; // break after this iteration.
     } else {
       if (max_occupied_addr) *max_occupied_addr = address + vmsize;
     }

Copy link

github-actions bot commented Jan 28, 2025

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

@thetruestblue thetruestblue force-pushed the fix_restrictmemorytomaxaddress branch from 58c430d to 27af192 Compare January 28, 2025 07:22
@thetruestblue thetruestblue force-pushed the fix_restrictmemorytomaxaddress branch 2 times, most recently from a7fa818 to e845f78 Compare January 29, 2025 05:26
@vitalybuka
Copy link
Collaborator

I abstain, and let folks more experienced in OSX to review.

@vitalybuka vitalybuka removed their request for review January 29, 2025 17:54
@@ -1272,10 +1272,11 @@ uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding,
mach_msg_type_number_t count = kRegionInfoSize;
kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth,
(vm_region_info_t)&vminfo, &count);
if (kr == KERN_INVALID_ADDRESS) {
if (kr == KERN_INVALID_ADDRESS || address > max_vm_address) {
Copy link
Collaborator

@yln yln Jan 29, 2025

Choose a reason for hiding this comment

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

Currently, if mach_vm_region_recurse() ever returns an error different from KERN_INVALID_ADDRESS (I don't think it currently does except if the arguments are wrong, then it returns KERN_INVALID_ARGUMENT), then we would still treat the result "successful enough" to update max_occupied_addr. Is that the intention or should it be kr != KERN_SUCCESS?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to changing the condition to kr != KERN_SUCCESS .

Copy link
Contributor Author

@thetruestblue thetruestblue Jan 29, 2025

Choose a reason for hiding this comment

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

I dont know if this is the case. For this condition we simply finishing checking that iteration. A different return code would probably be more properly handled by not doing that and potentially throwing an error. I also don't know if we would expect different kernel codes in other cases (entitlements?) I would rather do a follow up PR with this change to keep this change more limited.

Copy link
Contributor

@delcypher delcypher left a comment

Choose a reason for hiding this comment

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

Seems reasonable. I have a few nits though.

@delcypher
Copy link
Contributor

@thetruestblue I think it would be good to also explain a little in the commit message how this ended up working. The current description makes it sound like this never worked which I'm pretty sure isn't the case.

@thetruestblue
Copy link
Contributor Author

thetruestblue commented Jan 29, 2025

@thetruestblue I think it would be good to also explain a little in the commit message how this ended up working. The current description makes it sound like this never worked which I'm pretty sure isn't the case.

I only have a hypothesis for why this worked in the past, but don't have the historical experience to say I ever observed it working and I believe its been broken for some time. This code has been changed many times. I have a theory that the expectation that any space beyond max address used to return invalid, but now that value is not returned unless we surpass the expanded vm space available to some processes. I will add that to the commit message. But you are right, I do believe this worked in the past and should make that clear.

@thetruestblue thetruestblue force-pushed the fix_restrictmemorytomaxaddress branch from e845f78 to 96fce16 Compare January 30, 2025 23:00
There are two logic bugs breaking RestrictMemoryToMaxAddress -- adding left_padding within MapDynamicShadow.

There is also an issue with the expectation of hitting KERN_INVALID_ADDRESS when we are beyond the addressable regions.

For most embedded scenarios, we exceed vm_max_address and setting max_occupied address to a memory region the process doesn't have access to.

Because of this, our check if (new_max_vm < max_occupied_addr) { will always fail and we will never restrict the address on smaller devices.

rdar://66603866
@thetruestblue thetruestblue force-pushed the fix_restrictmemorytomaxaddress branch from 72dcb5b to 0cfcfc9 Compare January 30, 2025 23:08
@thetruestblue thetruestblue merged commit 50a5c4f into llvm:main Jan 30, 2025
5 of 6 checks passed
thetruestblue added a commit to swiftlang/llvm-project that referenced this pull request Feb 10, 2025
…ess (llvm#124712)

There are two logic bugs breaking RestrictMemoryToMaxAddress.
1. adding left_padding within MapDynamicShadow.
- RoundUpTo((uptr)free_begin + left_padding, alignment) already adjusts
for left padding. Adding this additionally within MapDynamicShadow
causes us to allocate a page larger than necessary.
- This incorrect calculation also means RestrictMemoryToMaxAddress will
never find a big enough gap.

2. There is also an issue with the expectation of hitting
KERN_INVALID_ADDRESS when we are beyond the addressable regions.
- For most embedded scenarios, we exceed vm_max_address without getting
KREN_INVALID_ADDRESS so we setting max_occupied_address to a memory
region the process doesn't have access to, beyond the max address, and
that space is never marked as available so we never find a valid gap in
those regions.
- At some point previous it seems the assumption was once we were beyond
the Max address we could expect KREN_INVALID_ADDRESS, which is no longer
true up through the extended space not given to most processes.
- Because of this, the check` if (new_max_vm < max_occupied_addr)` will
always fail and we will never restrict the address on smaller devices.
- Additionally because of the extra page added by adding left_padding,
and how we only minimally restrict the vm, there's a chance we restrict
the vm only enough for the correctly calculated size of shadow. In these
cases, restricting the vm max address and will always fail due to the
extra page added to space size.

credit to @delcypher for the left_padding diagnosis, remembered his old
radar and PR when investigating this. https://reviews.llvm.org/D85389

Will monitor closely for fall out.

rdar://66603866
(cherry picked from commit 50a5c4f)
thetruestblue added a commit to swiftlang/llvm-project that referenced this pull request Feb 11, 2025
[ASan]Fix logic bugs that break RestrictMemoryToMaxAddress (llvm#124712)
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.

6 participants