Skip to content

Fix race condition in memory_decommit/memory_reset on Apple ARM64#18423

Open
Drustburn wants to merge 6 commits intoRPCS3:masterfrom
Drustburn:fix/apple-arm64-memory-decommit-race
Open

Fix race condition in memory_decommit/memory_reset on Apple ARM64#18423
Drustburn wants to merge 6 commits intoRPCS3:masterfrom
Drustburn:fix/apple-arm64-memory-decommit-race

Conversation

@Drustburn
Copy link
Copy Markdown

Summary

On Apple ARM64, memory_decommit and memory_reset used a munmap followed by mmap without MAP_FIXED (since Apple rejects MAP_FIXED | MAP_JIT). Between the two calls, another thread could claim the unmapped address range, causing mmap to return a different address and triggering a fatal verification error.

Under concurrent load (e.g. PPU LLVM compilation with many worker threads), this race manifests reliably as Verification failed (object: 0x0) crashes across all PPUW threads.

Fix

Use MAP_FIXED without MAP_JIT instead. This atomically replaces the mapping without any window for other threads to interfere. The MAP_JIT attribute is lost on the replaced pages, but the application's code signing entitlements (allow-unsigned-executable-memory, disable-executable-page-protection) permit executable mappings without it.

Before (crashes under load)

ensure(::munmap(pointer, size) != -1);
ensure(::mmap(pointer, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0) == pointer);

After (atomic, race-free)

ensure(::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | c_map_noreserve, -1, 0) != reinterpret_cast<void*>(uptr{umax}));

Test plan

  • Tested on Apple M3 Max, macOS 26.3.1
  • Verified all PPUW memory_decommit verification failures are eliminated
  • PPU LLVM module loading completes without errors
  • SPU interpreter builds successfully

The previous approach used munmap followed by mmap without MAP_FIXED
(since Apple rejects MAP_FIXED | MAP_JIT). Between the two calls,
another thread could claim the unmapped address range, causing mmap
to return a different address and triggering a fatal verification error.

Under concurrent load (e.g. PPU LLVM compilation with many worker threads),
this race manifests reliably as "Verification failed (object: 0x0)" crashes
across all PPUW threads in memory_decommit.

Fix: Use MAP_FIXED without MAP_JIT instead. This atomically replaces the
mapping without any window for other threads to interfere. The MAP_JIT
attribute is lost on the replaced pages, but the application's code signing
entitlements (allow-unsigned-executable-memory, disable-executable-page-protection)
permit executable mappings without it.

Applied the same fix to memory_reset which had the identical pattern.
@AniLeo AniLeo requested a review from kd-11 March 22, 2026 09:55
Comment thread rpcs3/util/vm_native.cpp
Comment thread rpcs3/util/vm_native.cpp
The Apple ARM64 code paths are now identical to the generic case,
so the ifdef blocks are unnecessary.
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.

5 participants