Assertion failure: atomIsMarked(zone, symbol), at js/src/gc/AtomMarking-inl.h:101 (UnmarkGrayTracer traces weak WeakRef→Symbol edges into the per-zone atom-marking bitmap)
Categories
(Core :: JavaScript: GC, defect, P1)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox-esr115 | --- | unaffected |
| firefox-esr140 | --- | unaffected |
| firefox151 | --- | wontfix |
| firefox152 | + | fixed |
| firefox153 | + | fixed |
| firefox154 | + | fixed |
People
(Reporter: decoder, Assigned: jonco)
References
(Blocks 2 open bugs, Regression)
Details
(6 keywords, Whiteboard: [adv-main152.0.4+r])
Attachments
(2 files, 4 external files)
Summary
The gray-unmarking traversal (js::gc::UnmarkGrayTracer) traces weak edges (it only sets WeakMapTraceAction::Skip, not JS::WeakEdgeTraceAction::Skip), while regular GC marking does not. When unmark-gray walks a gray object that holds a WeakRef whose target is an atoms-zone JS::Symbol, WeakRefObject::trace (js/src/builtin/WeakRefObject.cpp:125) feeds that weak symbol edge to UnmarkGrayTracer::onChild<JS::Symbol> (js/src/gc/Marking.cpp:3224), which unconditionally calls
AtomMarkingRuntime::maybeUnmarkGrayAtomically(sourceZone, symbol)
That primitive is repair-only: it requires the symbol to already be present in the source zone's per-zone atom-marking bitmap (MOZ_ASSERT(atomIsMarked(zone, symbol)), js/src/gc/AtomMarking-inl.h:101) and then does zone->markedAtoms().atomicSetExistingBit(blackBit).
Because the WeakRef target edge is weak, GC marking never adds the symbol to the reading zone's bitmap; after a zone GC, refineZoneBitmapsForCollectedZones refines that bitmap entry to white (the symbol stays alive via another, uncollected zone). The precondition is then violated:
- debug builds: deterministic
Assertion failure: atomIsMarked(zone, symbol), at js/src/gc/AtomMarking-inl.h:101(a plainMOZ_ASSERT, compiled out in release). - release builds:
SparseBitmap::atomicSetExistingBit()(js/src/ds/Bitmap.h) performs__atomic_fetch_orthrough aBitBlock*that is only debug-asserted non-null — a null-relative write when the zone has no bitmap block for that arena, or, when the block exists, a spurious black bit for a symbol the zone does not strongly reference, corrupting the GC's per-zone atom liveness bookkeeping from pure content JS.
The PoC reaches this through the hand-written Ion JIT read barrier for inlined WeakMap.prototype.get: MacroAssembler::emitWeapMapBarrierFastPath detects the gray mark bit on the returned value and calls the out-of-line js::jit::WeakMapValueReadBarrier (js/src/jit/VMFunctions.cpp:3462), which runs gc::UnmarkGrayGCThingRecursively(cell) directly from JIT code with no VM-call validation layer in between. Note, however, that any gray-unmark entry point reaches the same bug; the root cause is in the tracer, not the JIT barrier.
Reproduction
Testcase: poc.js (attached).
m-c-asan-debug/dist/bin/js --fuzzing-safe poc.js
Deterministic (3/3 runs), no GC zeal needed. Shell version: product_version = 20260603-5cffec5992d3 (m-c 20260603, rev 5cffec5992d3, Linux x86_64 ASan debug build).
The testcase: (1) creates a second global/zone g that strongly holds Symbol("t"); (2) in the main zone, builds gray root → WeakMap → entry value V = { wr: new WeakRef(g.s) } so V is marked gray; (3) runs schedulezone(this); schedulezone('atoms'); gc('zone') so the main zone's atom bitmap entry for the symbol is refined to white while the symbol survives via g; (4) calls a warmed Ion-compiled m.get(k) on the weak map, whose barrier fast path gray-unmarks V and walks the weak WeakRef edge into maybeUnmarkGrayAtomically on the white bitmap entry.
The shell's grayRoot() stands in for the gray cross-compartment wrappers the cycle collector creates routinely in the browser, and schedulezone/gc('zone') for ordinary zonal GC scheduling; the ingredients (multiple globals, WeakMap, WeakRef, Symbol) are all standard content JS.
Crash signature
Full log in crash-debug.txt:
Assertion failure: atomIsMarked(zone, symbol), at js/src/gc/AtomMarking-inl.h:101
#1 js::gc::AtomMarkingRuntime::maybeUnmarkGrayAtomically(JS::Zone*, JS::Symbol*) gc/AtomMarking-inl.h:101
#2 js::gc::UnmarkGrayTracer<1u>::onChild<JS::Symbol>(JS::Symbol*) gc/Marking.cpp:3224
#11 js::WeakRefObject::trace(JSTracer*, JSObject*) builtin/WeakRefObject.cpp:125
#18 js::gc::UnmarkGrayTracer<1u>::unmark(JS::GCCellPtr) gc/Marking.cpp:3285
#24 JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr) gc/Marking.cpp:3322
#25 js::jit::WeakMapValueReadBarrier(js::gc::TenuredCell*, JS::Zone*) jit/VMFunctions.cpp:3462
#26 ([anon:js-executable-memory]+0x2d9b) <- Ion-compiled WeakMap.get fast path
Suggested fix
Either make the gray-unmark traversal skip weak edges (set JS::WeakEdgeTraceAction::Skip for UnmarkGrayTracer, matching the marking tracer), or make UnmarkGrayTracer::onChild<JS::Symbol> / maybeUnmarkGrayAtomically tolerate (skip) symbols that are not present in the source zone's bitmap. The invariant of the per-zone atom-marking bitmap ([SMDOC] in gc/AtomMarking.cpp) is that it over-approximates atoms reachable via edges that GC marking traces — gray-unmarking must not assume weak edges are in it.
Same family as the previously fixed Bug 2034837 / Bug 2042465 (atoms-zone symbol handling in the WeakMap read barrier), but a distinct defect in the tracer itself.
| Reporter | ||
Comment 1•5 months ago
|
||
| Reporter | ||
Comment 2•5 months ago
|
||
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Comment 3•5 months ago
|
||
Unmarking a cell shouldn't unmark things weakly referenced from it. This change
makes the unmarking tracer work the same as the mark marking tracer which also
doesn't mark through weak edges.
| Assignee | ||
Comment 4•5 months ago
|
||
Updated•5 months ago
|
| Assignee | ||
Comment 5•5 months ago
|
||
Setting csectype-wildptr and set-high for possible relative write based on null pointer in release builds as per description.
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Comment 6•5 months ago
|
||
Comment on attachment 9595457 [details]
(secure)
Security Approval Request
- How easily could an exploit be constructed based on the patch?: Very difficult.
- Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
- Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: All back to 146.
- If not all supported branches, which bug introduced the flaw?: Bug 1994871
- Do you have backports for the affected branches?: Yes
- If not, how different, hard to create, and risky will they be?: Should be trivial.
- How likely is this patch to cause regressions; how much testing does it need?: Unlikely. This is a very simple change that matches the gray unmarking tracer to the same logic as the main marking tracer.
- Is the patch ready to land after security approval is given?: Yes
- Is Android affected?: Yes
Comment 7•5 months ago
|
||
Set release status flags based on info from the regressing bug 1994871
Updated•5 months ago
|
Comment 8•5 months ago
|
||
The severity field for this bug is set to S3. However, the bug is flagged with the sec-high keyword.
:jonco, could you consider increasing the severity of this security bug?
For more information, please visit BugBot documentation.
Comment 9•5 months ago
|
||
The bug is marked as tracked for firefox152 (beta) and tracked for firefox153 (nightly). However, the bug still has low severity.
:sdetar, could you please increase the severity for this tracked bug? If you disagree with the tracking decision, please talk with the release managers.
For more information, please visit BugBot documentation.
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
Comment 10•5 months ago
|
||
Set release status flags based on info from the regressing bug 1994871
Updated•5 months ago
|
Comment 11•5 months ago
|
||
Comment 12•5 months ago
|
||
Comment 13•4 months ago
|
||
Please nominate this for Beta and Release uplift.
| Assignee | ||
Updated•4 months ago
|
Comment 14•4 months ago
|
||
firefox-beta Uplift Approval Request
- User impact if declined/Reason for urgency: Possible crash / security vulnerability.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: low
- Explanation of risk level: This is a small and simple fix that stops gray unmarking progressing through weak edges as intended.
- String changes made/needed?: None
- Is Android affected?: yes
| Assignee | ||
Comment 15•4 months ago
|
||
Unmarking a cell shouldn't unmark things weakly referenced from it. This change
makes the unmarking tracer work the same as the mark marking tracer which also
doesn't mark through weak edges.
Original Revision: https://phabricator.services.mozilla.com/D305484
Comment 16•4 months ago
|
||
firefox-release Uplift Approval Request
- User impact if declined/Reason for urgency: Possible crash / security vulnerability.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: low
- Explanation of risk level: This is a small and simple fix that stops gray unmarking progressing through weak edges as intended.
- String changes made/needed?: None
- Is Android affected?: yes
| Assignee | ||
Comment 17•4 months ago
|
||
Unmarking a cell shouldn't unmark things weakly referenced from it. This change
makes the unmarking tracer work the same as the mark marking tracer which also
doesn't mark through weak edges.
Original Revision: https://phabricator.services.mozilla.com/D305484
Updated•4 months ago
|
Updated•4 months ago
|
Comment 18•4 months ago
|
||
| uplift | ||
Updated•4 months ago
|
Updated•4 months ago
|
Updated•4 months ago
|
Comment 19•4 months ago
|
||
| uplift | ||
Updated•4 months ago
|
Updated•1 month ago
|
Updated•1 month ago
|
Comment 20•1 month ago
|
||
Comment 21•1 month ago
|
||
| bugherder | ||
Description
•