I’m running into this error while testing my code on a M1 Mac:
Bus error at address 0x102cfc000
aborting due to recursive panic
Here’s the test in question:
test "executable" {
var gpa = executable();
var allocator = gpa.allocator();
const memory = try allocator.alloc(u8, 256);
_ = memory;
// allocator.free(memory);
// try expect(gpa.detectLeaks() == false);
}
executable()
looks like this:
pub fn executable() std.heap.GeneralPurposeAllocator(.{}) {
return .{
.backing_allocator = .{
.ptr = undefined,
.vtable = &ExecutablePageAllocator.vtable,
},
};
}
Where ExecutablePageAllocator
is just a copy-and-pasted version of PageAllocator
that sets the executable flag.
The code works fine on Linux and on an Intel Mac. Initially I was getting an Access Denied error due to the absence of the MAP_JIT flag. After adding that now I get this new strange error.
Sze
January 28, 2025, 1:48am
2
I wonder whether these links are related:
opened 12:33AM - 09 Feb 21 UTC
closed 08:17AM - 18 Oct 21 UTC
We are using the PCRE2 regular expression library (version 10.36, and by extensi… on, SLJIT), and have noticed a change in behavior after macOS 11.2 came out. Now, any attempt to allocate memory with SLJIT fails. It's okay for us, as PCRE2 can fall back to not using JIT for an expression. However, I wanted to dig in and figure out _why_ it was suddenly failing.
It turns out that, with `sljitExecAllocator.c`, the `mmap()` with `MAP_JIT` call is succeeding, but the subsequent call to `mprotect()` (on line 185) fails with a generic "permission denied" error from the system. This _was_ working in macOS 11.0 and 11.1, but something changed with the 11.2 update. After spending a day not really being able to tell "why", we filed a feedback issue with Apple DTS.
Apple DTS provided the following response:
> I think the documentation could state this more clearly, but I think the
> basic answer here is that this configuration:
>
> const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
>
> ...is inherently invalid on Apple Silicon. That is, it is inherently
> invalid to mark a page "rwx". The documentation doesn't QUITE say that,
> but you can see the implications inside this article:
>
> <https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon?language=objc>
>
> "When memory protection is enabled, a thread cannot write to a memory
> region and execute instructions in that region at the same time. Apple
> silicon enables memory protection for all apps, regardless of whether
> they adopt the Hardened Runtime."
>
> "Each thread has its own set of access permissions for a given memory
> region. When you call mmap to create the memory region, the system
> initially configures it as readable and executable (R-X) for all
> threads. Calling pthread_jit_write_protect_np with the value false
> offers a secure and efficient way to simultaneously remove the
> executable permission and add the writable permission (RW-) for the
> current thread."
>
> "Important
> Because pthread_jit_write_protect_np changes only the current thread’s
> permissions, avoid accessing the same memory region from multiple
> threads. Giving multiple threads access to the same memory region opens
> up a potential attack vector, in which one thread has write access and
> another has executable access to the same region."
>
> If you read between the lines there, particularly with the last
> "important" block, the underlying issue here is that you simply CAN'T*
> have a "rwx" page on Apple Silicon. I'm not sure what your mprotect
> call was doing pre-11.2, but I don't think it was behaving correctly.
>
> *You might ask how Rosetta supports apps that DO rely on RWX on the same
> thread, and the answer is basically that it toggles between RW/RX on the
> "fly" based on what's actually happening. In rough terms, you can
> basically think of it as catching the mach exception generated by being
> the wrong state and then toggling the page state to the required state.
> Not something I would recommend trying to implement.
>
> Long story short, I think my recommendation here would basically be to
> pthread_jit_write_protect_np instead of mprotect when working with
> MAP_JIT pages on Apple Silicon.
We already apply the `pthread_jit_write_protect_np()` calls _before and after_ invoking PCRE2 to compile a regex. But, it sounds like they are saying that SLJIT needs to not call `mprotect()` when on Apple Silicon no matter what.
We're planning on making the relevant change in our copy of PCRE2 and SLJIT, but I wanted to bring this up to you in case you have other consumers of the library hit this issue in the future.
Thank you for your time!
https://forums.developer.apple.com/forums/thread/672804
1 Like
Thanks for the pointer. That’s precisely the problem. The memory is still protected after the call to mmap(). Since Allocator
currently does a @memset()
on memory it has obtained (#4298 ), alloc()
blows up. Hopefully I can work around this.
1 Like