Repository navigation
Hold released 1-16 MB guest allocations for 2 s before unmapping - #146
Merged
Merged
Conversation
## Problem
Ghost of Tsushima crashed in every run at the same kind of place: a job worker
faults reading a 1-2 MB block that had just been freed. One run instead
crashed in Metal's command-buffer completion handler, releasing a corrupted
object (`objc_release` on garbage), and a Wine thread crashed on the same
value.
## Cause
A use-after-free race in the game's job system as it runs here: one worker
`MEM_RELEASE`s a 1-2 MB block the main thread allocated while other workers are
still `memcpy`ing out of it (Madeira's view history: the view was deleted 3-8 ms
before the fault), with fastsync on or off. Freed guest-band VA is handed out
again at once -- to the game or to Metal / malloc -- so the late readers either
fault or scribble over someone else's objects. The root cause of the race
(job refcount / wait ordering under the emulator) is not found yet.
## Change
`build/ntdll-unix/virtual_ios.c`, NtFreeVirtualMemory (current process only):
a whole-view `MEM_RELEASE` (size 0, page-aligned base) of a private
(`is_view_valloc`, not SEC_FILE / SEC_IMAGE / system) 1-16 MB allocation in the
64-bit guest band (0x7000000000..0x7c00000000) returns success at once, but the
mapping stays committed for `MADEIRA_FREE_DELAY_MS` (default 2000 ms; 0 turns
it off) and is released afterwards, from the next NtFreeVirtualMemory call
after the delay. Above 128 MB held, entries are released at once until 128 MB
remain; at most 256 entries are held. A second release of a held base
returns STATUS_MEMORY_NOT_ALLOCATED, as for a freed region. Late readers find
valid memory, and nobody else gets that VA meanwhile. Log: `[free-delay] #N
release of ... held for 2000 ms` (first 8, then every 1024th). Catalog row
added.
Host test `tests/host/check-free-delay.py`: compiles the production helpers
against stub views / clock / NtFreeVirtualMemory and checks which releases are
held, the double release, the delayed and capped release through the bypass,
`MADEIRA_FREE_DELAY_MS=0`, and the call site.
## Evidence
iPhone 17 Pro Max, iOS 27, Ghost of Tsushima: before, every run died on the
freed-while-used fault (or the Metal completion-handler crash); with the delay
the opening scene played for about 3 minutes, horse riding and the hand-over
to the player worked, the menu opened and the game saved; `[free-delay]` held
8 releases of 1-8 MB in that run. The delay stayed on by default in the fork
afterwards for God of War, GTA V Enhanced and 32-bit games without issues
attributed to it.
## Notes / risks
- This is a mitigation, not a root-cause fix; `MADEIRA_FREE_DELAY_MS=0`
reproduces the race for whoever looks for it.
- Memory freed by the game stays mapped up to 2 s longer (<= 128 MB at a
time), which costs footprint under memory pressure.
- During the delay the VA still shows as allocated: a `VirtualQuery` of a
held block reports it committed, and a fixed-address allocation over it
would fail until it is released. No such case was seen.
- The fork commit 80bcf2d ("place 64-bit views above the 32-bit windows when
the band below is full", same Ghost of Tsushima runs) is not ported:
upstream's lazy WoW64 windows reserve no placeholder in a 64-bit-only
session, so the band confinement it fixed does not happen there, and
upstream deliberately keeps the side above the lowest slots as the [cage]
holdback.
- Not compiled for iOS here; a differential `clang -fsyntax-only` of
virtual_ios.c against upstream shows no new diagnostics beyond the
stubbed-header environment. `check-config-catalog.py` needs the submodules
and was not run; the row was rendered with the generator's own code.
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189oLHghpaYKLk4f786a6bc
Signed-off-by: bahacan16 <190844990+bahacan16@users.noreply.github.com>
willfaust
pushed a commit
that referenced
this pull request
Oct 4, 2026
Squashed from #146. Signed-off-by: bahacan16 <190844990+bahacan16@users.noreply.github.com>
willfaust
added a commit
that referenced
this pull request
Oct 4, 2026
MADEIRA_FREE_DELAY_MS (#146) now defaults to 0. It works around one game's race and holds up to 128 MB more in every game, against the 4096 MB jetsam limit of 6 GB iPhones; set env.MADEIRA_FREE_DELAY_MS = 2000 in madeira.cfg or a game's own config to use it. The duplicate check runs again under the lock before a block is held: two threads releasing the same base could both hold it, and the drain then freed it twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Ghost of Tsushima crashed in every run at the same kind of place: a job worker faults reading a 1-2 MB block that had just been freed. One run instead crashed in Metal's command-buffer completion handler, releasing a corrupted object (
objc_releaseon garbage), and a Wine thread crashed on the same value.Cause
A use-after-free race in the game's job system as it runs here: one worker
MEM_RELEASEs a 1-2 MB block the main thread allocated while other workers are stillmemcpying out of it (Madeira's view history: the view was deleted 3-8 ms before the fault), with fastsync on or off. Freed guest-band VA is handed out again at once -- to the game or to Metal / malloc -- so the late readers either fault or scribble over someone else's objects. The root cause of the race (job refcount / wait ordering under the emulator) is not found yet.Change
build/ntdll-unix/virtual_ios.c, NtFreeVirtualMemory (current process only): a whole-viewMEM_RELEASE(size 0, page-aligned base) of a private (is_view_valloc, not SEC_FILE / SEC_IMAGE / system) 1-16 MB allocation in the 64-bit guest band (0x7000000000..0x7c00000000) returns success at once, but the mapping stays committed forMADEIRA_FREE_DELAY_MS(default 2000 ms; 0 turns it off) and is released afterwards, from the next NtFreeVirtualMemory call after the delay. Above 128 MB held, entries are released at once until 128 MB remain; at most 256 entries are held. A second release of a held base returns STATUS_MEMORY_NOT_ALLOCATED, as for a freed region. Late readers find valid memory, and nobody else gets that VA meanwhile. Log:[free-delay] #N release of ... held for 2000 ms(first 8, then every 1024th). Catalog row added.Host test
tests/host/check-free-delay.py: compiles the production helpers against stub views / clock / NtFreeVirtualMemory and checks which releases are held, the double release, the delayed and capped release through the bypass,MADEIRA_FREE_DELAY_MS=0, and the call site.Evidence
iPhone 17 Pro Max, iOS 27, Ghost of Tsushima: before, every run died on the freed-while-used fault (or the Metal completion-handler crash); with the delay the opening scene played for about 3 minutes, horse riding and the hand-over to the player worked, the menu opened and the game saved;
[free-delay]held 8 releases of 1-8 MB in that run. The delay stayed on by default in the fork afterwards for God of War, GTA V Enhanced and 32-bit games without issues attributed to it.Notes / risks
MADEIRA_FREE_DELAY_MS=0reproduces the race for whoever looks for it.VirtualQueryof a held block reports it committed, and a fixed-address allocation over it would fail until it is released. No such case was seen.clang -fsyntax-onlyof virtual_ios.c against upstream shows no new diagnostics beyond the stubbed-header environment.check-config-catalog.pyneeds the submodules and was not run; the row was rendered with the generator's own code.🤖 Generated with Claude Code
Claude-Session: https://claude.ai/code/session_0189oLHghpaYKLk4f786a6bc