Repository navigation
fix(android): treat SEGV_MAPERR like SEGV_ACCERR in the write-tracking fault path - #41
ptichalouf wants to merge 1 commit into
Conversation
Follow-up: independent verification of the decision logicThe change is one line, so I built a small ad-hoc control rather than relying on the device run alone. It extracts the real The driver reproduces the device situation: the guest-heap page is mapped and resident, and the kernel reports a given
All 8 expectations pass. Two things make the control meaningful rather than decorative:
Two limits, stated plainly:
The control and its transcript are in the PR's working folder if you want to run them. |
…g fault path The guest-heap write tracking (plat_written_pages) protects pages read-only while the guest writes to them. When mprotect() splits a VMA, the kernel can resolve the concurrent guest write against a VMA that is being replaced and report SEGV_MAPERR (si_code=1) even though the page stays mapped and resident. The crash handler only tested SEGV_ACCERR, so the fault was treated as fatal and the game died instead of taking the write-access path. Measured on a Galaxy Z Fold4 (arm64, 4 KiB pages): A memset(352 KiB) on an r--p page, alone 0/20 (always ACCERR) B same + one thread mprotects the same page 20/20 (not recoverable) C same + one thread mprotects a different page 20/20 (not recoverable) D same, one-byte write 3/20 The upstream binary fault trace confirms the kernel itself reports si_code=1 (sig=11 si_code=1 addr=0x7285dc5000, x0=dst x1=0 x2=0x56000, i.e. a memset(dst, 0, 352 KiB)), so the code is not rewritten by the signal chain. wt_fault() is safe on a genuinely unmapped page: mprotect() returns ENOMEM and the 64 KiB neighbourhood attempt fails too, since mprotect() needs the whole range mapped. A real hole therefore still falls through to the diagnostic path below. With the fix, the same build runs 14611 presented frames with 0 crashes, 0 heap faults and 0 signals, against a systematic SIGSEGV at 3 presented frames before.
4d08cdb to
9f2836c
Compare
Update: the comment was tightened, and the control was re-run against the published blobTwo changes since the previous comment: 1. The comment in the code was shortened ( 2. The control was re-run against the published blob, not against a working copy. The harness now takes
The two deliberately broken variants are still rejected: The limits from the previous comment still stand unchanged: the |
|
Confirmed on real hardware — this PR's diagnosis matches a crash we hit on a Huawei tablet. Device: Huawei MatePad 11 (GOT-W29), HarmonyOS 4 / Android 12 (API 31), Snapdragon 870 (SM8250), Adreno 650, 8 GB RAM, not rooted After a clean setup the game boots and plays, but it dies intermittently across several game threads (
Workaround check: As a control: the same APK with the same game files runs fine on a non-Huawei Android phone, so this is ROM/kernel-specific — Huawei's kernel resolves these guest writes as MAPERR where others report ACCERR. Happy to test a build with this patch on the tablet if useful. |
Problem
The Android runtime tracks which guest-heap pages the OS wrote to, and protects
them read-only so the guest can be told about the write later
(
plat_written_pages->mprotect(PROT_READ)). The crash handler only tookthe write-access recovery path for
SEGV_ACCERR:When
mprotect()splits a VMA, the kernel can resolve the concurrent guestwrite against a VMA that is being replaced and report
SEGV_MAPERR(
si_code=1) - even though the page stays mapped and resident. That fault wastherefore treated as fatal: the game died with a SIGSEGV instead of recovering
the write access.
Measured on a Galaxy Z Fold4 (arm64, 4 KiB pages), 20 repetitions per arm:
memset(352 KiB)on anr--ppage, alonemprotects the same pagemprotects a different pageArm A matters: a single-threaded probe on the same page and the same size
never produces MAPERR. The concurrency is the discriminating factor.
The
si_codereally comes from the kernelThe upstream binary fault trace captures the
siginfoat the first frontassembly, before any signal chaining, so it cannot be rewritten by the ART
chain. From a run without the fix:
The faulting instruction is a
memset(dst, 0, 352 KiB), and the kernel reportssi_code=1.Fix
Treat both codes the same way - the page needs its write access back:
This is safe on a genuinely unmapped page:
mprotect()returnsENOMEM, andthe 64 KiB neighbourhood attempt fails too, since
mprotect()needs the wholerange mapped. A real hole still falls through to the diagnostic path below, so
there is no silent corruption and no infinite loop.
Result
Same build, same device:
The fault trace file, the probe sources (
kernel_probe6.c,kernel_probe7.c,arm64 NDK r27c) and the device logs are available on request.
Scope
One file, one line of logic. The comment style matches the rest of this file.
No behaviour change for a page that is genuinely unmapped, and no change to the
SEGV_ACCERRpath.