[pcre-dev] [Bug 2618] JIT crash in macOS 11 with ARM64 hardw…

Top Page
Delete this message
Author: admin
Date:  
To: pcre-dev
Old-Topics: [pcre-dev] [Bug 2618] New: Patch with JIT support for macOS ARM64 hardware
Subject: [pcre-dev] [Bug 2618] JIT crash in macOS 11 with ARM64 hardware
https://bugs.exim.org/show_bug.cgi?id=2618

--- Comment #16 from Saagar Jha <saagar@???> ---
(In reply to Carlo Marcelo Arenas Belón from comment #15)
> FWIW we had been setting MAP_JIT for macOS for several releases and we had not
> seen a report of a process killed for codesigning violation until the recent
> crash logs (sadly they have no debug information, but would seem to be the
> case)


Hardened runtime is a new thing in Mojave, so that might be why you weren't
seeing it earlier.

> iOS, tvOS, ipadOS and all other sibling architectures should be triggering the
> same, but would be important to test that to make sure the issue is because of
> the recent change in the API to ALSO require pthread_jit_write_protect_np() and
> not the way the apple arm processors with APRR (which seems to be at least
> available for maybe the last 3 generations) behave with MAP_JIT (which has
> also been available for several releases, even if not really documented).


iOS/tvOS/iPadOS doesn't let you create a RWX region at all without
dynamic-codesigning, which only Safari has, thus there is no need to expose
this to API third-party applications on the embedded platforms (Apple uses a
different private call for Safari that does essentially the same thing).
Switching between RW- and R-X using mprotect while CS_DEBUGGED has gone away in
iOS 14 too so there really is no way to create code at runtime anymore that I
know of. You'll need to disable JIT there entirely. "Full APRR" is new in A12
and the specific register used for fast permission switching was added in A11
(iPhone X, 2017).

> note the crash points to a page with RWX permissions though but that might be
> a sideeffect of how the ARM mask permission is applied on top of what the OS
> reports.


Right, the effective page permissions diverge from the "underlying" page
permissions as known by the virtual memory APIs due to the new mask being
applied.

> probably a rhetorical question, but any idea why it seems to be backwards?
> it makes more sense to have the permission initially be RW so you can write
> the code before moving to RX so it can be executed.


I thought this was strange too, but Stuart reminded me that this feature is
intended to be a mitigation against an attacker who has an arbitrary write and
can spray code into the JIT region. As such, it should be marked as
execute-only (and thus unwritable) at all times except for the short window
where new code is being written to that region, otherwise an attacker would be
able to add some code there and it would be run the next time the region was
made executable. To protect against hijacking early in process startup, even
before there's a chance for the appropriate call to be made to
pthread_jit_write_protect_np, the page must start off as execute-only.

> indeed, I just implemented something similar for NetBSD W^X and the patch to
> PCRE (including a whole new "allocator") seems simpler than what macOS might
> need.
>
> mmap a region RW and then mprotecting it to RX after filling it would work?
> it would seem that mmap succeeded though so is a fault the only way to know?
>
> AFAIK there are entitlements to cover that transition which I'll presume
> should allow at least Intel to work if codesigning, would the same apply to
> ARM?


Using mprotect to flip the permissions, as long as W^X is maintained, should
work without any additional code changes, although it will be slower than using
the new pthread API. You will need an entitlement when running under the
Hardened Runtime, IIRC it is
com.apple.security.cs.allow-unsigned-executable-memory.

> this is what fits better our model, any insights on what would happen though
> if the application is single threaded?, is linking with pthread the way to
> have pthread_jit_write_protect_np available and therefore single threaded
> applications can't do JIT except for the option below?
>
> is there a source for this available online?, is there an "equivalent"
> snippet for Intel or another way to do single threaded JIT?


A couple things: one, on macOS you basically "always" link against pthread,
it's part of libSystem that pretty much every application links against by
default. Just include pthread.h and make sure you are not linking statically
(it's hard to mess this up accidentally) and you're good. Another: I think you
might be a bit confused about how this works with threading; the answer is that
the "mask" can be treated as being specific to your thread. Single-threaded,
multi-threaded, it doesn't matter, as long as you keep track of your current
state from that thread–writing, or executing–for your JIT regions using the
right calls. You can think of pthread_jit_write_protect_np doing the equivalent
of setting a thread-local variable that says how you can access the RWX region.
In a single-threaded program that means that with Apple's API you can only do
one or the other at once, but you can imagine a multi-threaded program that
shares one JIT region and one thread has used the function to say "I want to
have execute permission" and one thread says "I want to have write permission"
and they can each do their thing independently (not that I would recommend
this, as it would be complicated to synchronize…) More likely is that if you
touch the region from multiple threads you will want to ensure the same
permissions, and when you want to switch you will do it from all threads.

Here is some more information about this proprietary register from unofficial
sources (I read these to create that snippet above :P):
https://siguza.github.io/APRR/,
https://www.sstic.org/media/SSTIC2019/SSTIC-actes/WEN_ETA_JB/SSTIC2019-Slides-WEN_ETA_JB-benoist-vanderbeken_perigaud.pdf

> am I wrong in consider pthread_jit_write_protect_np() calls something that
> needs to be done regardless of CPU type as it seems to be mandated by the
> API (included in macOS 11, and some yet undetermined versions for iOS and
> friends)


In summary: not required or even available on embedded platforms because you
shouldn't be able to allocate RWX at all. Available on macOS 11.0+, on Intel
does nothing because it is not necessary and on Apple silicon does the masking
dance I mentioned earlier.

> for pcre (legacy or 8.x) the best option is (as was done already in brew) to
> disable jit at build time, but failing to detect a broken allocator in
> pcre_study() is another bug that I am worried might be probably a side
> effect of the way the mmap/mprotect calls are failing to report failures and
> that should be raised with Apple


That is what MacPorts is doing now as well, until we can reenable the JIT.
Detecting a "broken" allocator can be done by checking for a SIGBUS when
accessing that page (the virtual memory subsystem will "lie" to you about it,
because it is entirely unaware of this by design AFAIK).

--
You are receiving this mail because:
You are on the CC list for the bug.