Assertion failure: this->flags() == 0, at /js/src/gc/Cell.h:800 with Debugger and wasm
Categories
(Core :: JavaScript: WebAssembly, defect, P1)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox-esr115 | --- | unaffected |
| firefox-esr140 | --- | unaffected |
| firefox148 | --- | wontfix |
| firefox149 | --- | wontfix |
| firefox150 | + | fixed |
People
(Reporter: decoder, Assigned: rhunt)
Details
(8 keywords, Whiteboard: [bugmon:update,bisected,confirmed][adv-main150+])
Attachments
(4 files, 2 external files, 2 obsolete files)
The following testcase crashes on mozilla-central revision 20260130-20b1a1e6c530 (fuzzing-debug build, run with --fuzzing-safe --no-threads):
See attachment.
Backtrace:
==1750025==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5887bf3e04ab bp 0x7ffc277df4f0 sp 0x7ffc277df4f0 T1750025)
==1750025==The signal is caused by a WRITE memory access.
==1750025==Hint: address points to the zero page.
#0 0x5887bf3e04ab in MOZ_CrashSequence /builds/worker/workspace/obj-build/dist/include/mozilla/Assertions.h:237:3
#1 0x5887bf3e04ab in headerPtr /js/src/gc/Cell.h:800:5
#2 0x5887bf3e04ab in shape /js/src/vm/JSObject.h:94:37
#3 0x5887bf3e04ab in JSObject::getClass() const /js/src/vm/JSObject.h:115:44
#4 0x5887c0b7b604 in is<WasmValueBox> /js/src/vm/JSObject.h:512:12
#5 0x5887c0b7b604 in js::wasm::AnyRef::toJSValue() const /js/src/wasm/WasmAnyRef.cpp:121:13
#6 0x5887c0d0d95a in js::wasm::DebugFrame::getLocal(unsigned int, JS::MutableHandle<JS::Value>) /js/src/wasm/WasmDebugFrame.cpp:120:34
#7 0x5887c0a50c48 in (anonymous namespace)::DebugEnvironmentProxyHandler::handleUnaliasedAccess(JSContext*, JS::Handle<js::DebugEnvironmentProxy*>, JS::Handle<js::EnvironmentObject*>, JS::Handle<JS::PropertyKey>, (anonymous namespace)::DebugEnvironmentProxyHandler::Action, JS::MutableHandle<JS::Value>, (anonymous namespace)::DebugEnvironmentProxyHandler::AccessResult*) const /js/src/vm/EnvironmentObject.cpp:1943:27
#8 0x5887c0a2af0c in getMaybeSentinelValue /js/src/vm/EnvironmentObject.cpp:2380:10
#9 0x5887c0a2af0c in js::DebugEnvironmentProxy::getMaybeSentinelValue(JSContext*, JS::Handle<js::DebugEnvironmentProxy*>, JS::Handle<JS::PropertyKey>, JS::MutableHandle<JS::Value>) /js/src/vm/EnvironmentObject.cpp:2655:50
#10 0x5887bf7ac999 in js::DebuggerEnvironment::getVariable(JSContext*, JS::Handle<js::DebuggerEnvironment*>, JS::Handle<JS::PropertyKey>, JS::MutableHandle<JS::Value>) /js/src/debugger/Environment.cpp:602:12
#11 0x5887bf7ac6d4 in js::DebuggerEnvironment::CallData::getVariableMethod() /js/src/debugger/Environment.cpp:324:10
#12 0x5887bf7ae781 in bool js::DebuggerEnvironment::CallData::ToNative<&js::DebuggerEnvironment::CallData::getVariableMethod()>(JSContext*, unsigned int, JS::Value*) /js/src/debugger/Environment.cpp:140:10
#13 0x5887c07940c4 in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /js/src/vm/Interpreter.cpp:490:13
#14 0x5887c079396f in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /js/src/vm/Interpreter.cpp:586:12
#15 0x5887c07a4652 in CallFromStack /js/src/vm/Interpreter.cpp:658:10
#16 0x5887c07a4652 in js::Interpret(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:3272:16
#17 0x5887c0792fea in js::RunScript(JSContext*, js::RunState&) /js/src/vm/Interpreter.cpp:460:13
#18 0x5887c0793995 in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /js/src/vm/Interpreter.cpp:618:13
#19 0x5887c0794d6c in js::Call(JSContext*, JS::Handle<JS::Value>, JS::Handle<JS::Value>, js::AnyInvokeArgs const&, JS::MutableHandle<JS::Value>, js::CallReason) /js/src/vm/Interpreter.cpp:685:8
#20 0x5887bf5caf66 in js::Call(JSContext*, JS::Handle<JS::Value>, JSObject*, JS::Handle<JS::Value>, JS::Handle<JS::Value>, JS::MutableHandle<JS::Value>) /js/src/vm/Interpreter.h:145:10
#21 0x5887bf7774ac in js::Debugger::fireExceptionUnwind(JSContext*, JS::Handle<JS::Value>, js::ResumeMode&, JS::MutableHandle<JS::Value>) /js/src/debugger/Debugger.cpp:2299:13
#22 0x5887bf772669 in operator() /js/src/debugger/Debugger.cpp:1391:21
#23 0x5887bf772669 in operator() /js/src/debugger/Debugger.cpp:887:32
#24 0x5887bf772669 in operator() /js/src/debugger/Debugger.cpp:855:61
#25 0x5887bf772669 in enterDebuggerHook<(lambda at /js/src/debugger/Debugger.cpp:855:38)> /js/src/debugger/Debugger.h:1066:10
#26 0x5887bf772669 in dispatchHook<(lambda at /js/src/debugger/Debugger.cpp:886:23)> /js/src/debugger/Debugger.cpp:855:16
#27 0x5887bf772669 in dispatchResumptionHook<(lambda at /js/src/debugger/Debugger.cpp:1389:7)> /js/src/debugger/Debugger.cpp:885:10
#28 0x5887bf772669 in js::DebugAPI::slowPathOnExceptionUnwind(JSContext*, js::AbstractFramePtr) /js/src/debugger/Debugger.cpp:1387:30
#29 0x5887c0ccc217 in onExceptionUnwind /js/src/debugger/DebugAPI-inl.h:165:12
#30 0x5887c0ccc217 in js::wasm::HandleExceptionWasm(JSContext*, js::JitFrameIter&, js::jit::ResumeFromException*) /js/src/wasm/WasmBuiltins.cpp:883:12
#31 0x5887c05a20af in js::jit::HandleException(js::jit::ResumeFromException*) /js/src/jit/JitFrames.cpp:758:7
#32 0x5887c0cd08a7 in WasmHandleThrow(js::jit::ResumeFromException*) /js/src/wasm/WasmBuiltins.cpp:929:3
#33 0x2855d6715544 ([anon:js-executable-memory]+0x544)
==1750025==Register values:
rax = 0x0000000000000000 rbx = 0x000012485d201cd0 rcx = 0x0000000000000320 rdx = 0x00007ea0e3204563
rdi = 0x00007ea0e3205700 rsi = 0x0000000000000000 rbp = 0x00007ffc277df4f0 rsp = 0x00007ffc277df4f0
r8 = 0x0000000000000000 r9 = 0x0000000000000003 r10 = 0x0000000000000002 r11 = 0x0000000000000293
r12 = 0x0000000000000013 r13 = 0x0000000000000000 r14 = 0x0000000000000001 r15 = 0x0000000000000030
UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV /builds/worker/workspace/obj-build/dist/include/mozilla/Assertions.h:237:3 in MOZ_CrashSequence
==1750025==ABORTING
This bug is part of a series of bugs reported by Anthropic, thank you for your contributions to Firefox!
| Reporter | ||
Comment 1•9 months ago
|
||
| Reporter | ||
Comment 2•9 months ago
|
||
Comment 3•9 months ago
|
||
Verified bug as reproducible on mozilla-central 20260130211306-fda5656fd125.
Unable to bisect testcase (Testcase reproduces on start build!):
Start: d87cd04072b041911d218cec1baf59de7f2a5ede (20251103213617)
End: 20b1a1e6c530f3506d5378bf5f4da504b6f03661 (20260130092147)
BuildFlags: BuildFlags(asan=True, tsan=False, debug=False, fuzzing=True, coverage=False, valgrind=False, no_opt=False, fuzzilli=False, nyx=False, searchfox=False, afl=False)
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
| Assignee | ||
Updated•8 months ago
|
Comment 4•8 months ago
|
||
A reduced test case. Note that changing the type of (wasm) function
testTraps argument from externref to i32 causes the failure to
disappear, and also for valgrind to no longer complain that we're reading
from a poisoned Nursery.
Updated•8 months ago
|
Comment 5•8 months ago
|
||
Here's a slightly revised testcase and a summary of what happened.
The essence is that we allocate a GC object (any kind will do) by calling mkS
(from JS-land):
(type $S (struct (field i32)))
(func (export "mkS") (result structref)
struct.new_default $S
)
Then, in another call from JS-land it is passed up a chain of functions,
beginning at three:
(func $zero (export "zero") (param structref)
(unreachable)
)
(func $one (export "one") (param structref)
(local.get 0) (call $zero)
)
(func $two (export "two") (param structref)
(local.get 0) (call $one)
)
(func $three (export "three") (param structref)
(local.get 0) (call $two)
)
The chain of function calls exists only to make the stack traces clearer.
We arrive at zero, which traps. zeros frame is overwritten (by who?) with
a frame from code generated by GenerateThrowStub (written (GenerateThrowStub)
below).
This calls onwards, per the dbg.onExceptionUnwind setting, and eventually
does a moving GC. This appears to cause a call to Instance::traceFrame (and
Instance::updateFrameForMovingGC) for the frame (GenerateThrowStub) but with
a nextPC for the zero frame, that has by now been overwritten. This causes
no stackmap to be found and I suspect is closely related to the missing update
of the stack local.
Subsequently env.getvariable(n) causes DebugFrame::getLocal to read this
non-updated ref, which points into a poisoned Nursery, so we crash soon after.
This is difficult to debug because I don't know what was supposed to have
happened. In particular, is it intended that a frame that traps gets replaced
with a frame from (GenerateThrowStub) ?
Comment 6 contains supporting details and further questions at the end.
Comment 6•8 months ago
•
|
||
More details and some questions, in support of the summary in comment 5.
Note these are from an x86_32-linux build, to avoid any complexities
resulting from the amd64-ELF ABI red zone.
The stack at the time GC happens, as shown by GDB:
(further in)
#20 0x59cf9be7 js::DebugAPI::onExceptionUnwind js/src/debugger/DebugAPI-inl.h:165
#21 0x5a665146 js::wasm::HandleExceptionWasm js/src/wasm/WasmBuiltins.cpp:887
#22 0x59cd9c70 js::jit::HandleException (rfe=0xffffa0d8) js/src/jit/JitFrames.cpp:758
#23 0x5a66ae26 WasmHandleThrow (rfe=0xffffa0d8) js/src/wasm/WasmBuiltins.cpp:933
#24 0x437063e2 ??? ()
#25 0x43726370 ??? ()
#26 0x43726470 ??? ()
#27 0x43726570 ??? ()
#28 0x437268e9 ??? ()
#29 0x5a70c9ac js::wasm::Instance::callExport js/src/wasm/WasmInstance.cpp:3979
#30 0x5a70bf85 WasmCall js/src/wasm/WasmInstance.cpp:3765
#31 0x59f01af9 CallJSNative js/src/vm/Interpreter.cpp:490
(further out)
and as shown by Valgrind, including JITted function names. We can see that GDB
and V agree on the stack, modulo page numbers:
(further in)
0x77A4BE6 js::DebugAPI::onExceptionUnwind js/src/debugger/DebugAPI-inl.h:165
0x8110145 js::wasm::HandleExceptionWasm js/src/wasm/WasmBuiltins.cpp:887
0x7784C6F js::jit::HandleException js/src/jit/JitFrames.cpp:758
0x8115E25 WasmHandleThrow js/src/wasm/WasmBuiltins.cpp:933
0x4CE2B3E1 wasmSharedStubs:Throw@..4ce2b3c0 (in ???)
0x4CE6B36F wasmBL:fI=2:one (in ???)
0x4CE6B46F wasmBL:fI=3:two (in ???)
0x4CE6B56F wasmBL:fI=4:three (in ???)
0x4CE6B8E8 wasmBL:fI=4:InterpEntry@..4ce6b8a0 (in ???)
0x81B79AB js::wasm::Instance::callExport js/src/wasm/WasmInstance.cpp:3979
0x81B6F84 WasmCall js/src/wasm/WasmInstance.cpp:3765
0x79ACAF8 CallJSNative js/src/vm/Interpreter.cpp:490
(further out)
Everything after his point pertains to the Valgrind stack.
Code ranges of the relevant JITted functions are as follows. Note this isn't
a stack.
C::aCB == Code::addCodeBlock
C::aCB [c:0x4ce2b3c0-c:0x4ce2b3e7) 39 wasmSharedStubs:Throw@..4ce2b3c0
== (GenerateThrowStub)
C::aCB [c:0x4ce6b040-c:0x4ce6b1f7) 439 wasmBL:fI=0:wasm-function[0]
C::aCB [c:0x4ce6b200-c:0x4ce6b2c0) 192 wasmBL:fI=1:zero
C::aCB [c:0x4ce6b2c0-c:0x4ce6b3b9) 249 wasmBL:fI=2:one
C::aCB [c:0x4ce6b3c0-c:0x4ce6b4b9) 249 wasmBL:fI=3:two
C::aCB [c:0x4ce6b4c0-c:0x4ce6b5b9) 249 wasmBL:fI=4:three
C::aCB [c:0x4ce6b5d0-c:0x4ce6b647) 119 wasmBL:fI=0:InterpEntry@..4ce6b5d0
Here's an analysis of code addresses passed to Instance::traceFrame. The first
entry is the important one.
(further in)
Instance::traceFrame frameBase=s:0xfe87f198 nextPC=c:0x4ce6b29b map=d:(nil)
# function = (from GenerateThrowStub)
# but nextPC (0x4ce6b29b) isn't from this function
# it is inside "zero"
# So the unwinder is in the frame for (GenerateThrowStub) but we are presented
# with a PC from "zero" instead
Instance::traceFrame frameBase=s:0xfe87f1f8 nextPC=c:0x4ce6b370 map=d:0xaf89520
frame=s:0xfe87f1b0-s:0xfe87f210
# function = "one", nextPC valid
Instance::traceFrame frameBase=s:0xfe87f258 nextPC=c:0x4ce6b470 map=d:0xaf895c0
frame=s:0xfe87f210-s:0xfe87f270
# function = "two", nextPC valid
Instance::traceFrame frameBase=s:0xfe87f2b8 nextPC=c:0x4ce6b570 map=d:0xaf89660
frame=s:0xfe87f270-s:0xfe87f2d0
# function = "three", nextPC valid
(further out)
Here's a reconstruction of the frame boundaries:
DebugFrame::getLocal: reading ref at 0xfe87f1a8
# This is the local ref that apparently doesn't get updated across GC
# Frame boundaries at time of GC are
(further out)
frame=s:0xfe87f270-s:0xfe87f2d0 // three
frame=s:0xfe87f210-s:0xfe87f270 // two
frame=s:0xfe87f1b0-s:0xfe87f210 // one
frameBase=s:0xfe87f198 // (GenerateThrowStub)
.. ASSUMING.. frame=s:0xfe87f190-s:0xfe87f1b0
(further in)
# So the not-updated local is in the frame of (GenerateThrowStub)
Finally, some questions resulting from all this:
(0) Is it indeed correct, that a wasm frame that traps gets overwritten by a
frame from (GenerateThrowStub) ?
(1) Where did DebugFrame::getLocal get the local address 0xfe87f1a8
from? Did it really mean to query that, or is it the address of
the local in the (now-overwritten) function zero frame?
(2) When unwinding, why are we given a nextPC for (GenerateThrowStub) which
is again from the overwritten zero frame?
(3) Even if (2) were fixed so that nextPC was actually inside
(GenerateThrowStub), does it have a stackmap? Even if it does, the
jit::ResumeFromException structure that is really the only thing in
(GenerateThrowStub)'s frame contains two JS::Values; everything else is
scalar. Can the wasm stackmap mechanism even handle a JS::Value rather
than a standard GC-thing pointer? I don't think so.
Updated•8 months ago
|
Comment 7•8 months ago
•
|
||
After some discussion with Jan, it appears that the reduced test case from
comment 5 fails because we don't create a stackmap for the wasm unreachable
opcode. And the original test case from decoder (which I have now
un-obsoleted) also fails because there's no stackmap for the integer
divide-by-zero trap.
The following ultra-kludge makes both test cases run successfully. As a
general fix it seems to me we'd need to create a stackmap for every trap insn
we create in debug mode. Maybe we can make BaseCompiler::trap do that,
since it can easily enough consult compilerEnv_.debugEnabled().
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
index dc5950441a..efdfcc0c32 100644
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -294,6 +294,8 @@
Label nonZero;
masm.branchTest32(Assembler::NonZero, rhs, rhs, &nonZero);
trap(Trap::IntegerDivideByZero);
+ (void/*FIXME*/)createStackMap("BC::checkDivideByZero(RegI32)",
+ HasDebugFrameWithLiveRefs::Maybe);
masm.bind(&nonZero);
}
@@ -10573,6 +10588,10 @@
CHECK(iter_.readUnreachable());
if (!deadCode_) {
trap(Trap::Unreachable);
+ if (!createStackMap("unreachable",
+ HasDebugFrameWithLiveRefs::Maybe)) {
+ return false;
+ }
deadCode_ = true;
}
NEXT();
Comment 8•8 months ago
|
||
In the wasm baseline compiler, generate stackmaps when JS debugging is enabled,
for some cases that were previously missing:
-
traps resulting from integer division (or remainder) by zero
-
traps resulting from integer division (or remainder) overflow
-
the wasm
unreachableopcode
Actual changes are:
-
BaseCompiler::trap is renamed to BaseCompiler::trapWithoutStackMap; no change
of behaviour (it just generates a trap insn) -
new method BaseCompiler::trapWithDebugStackMap, which also generates a trap
instruction, and, if JS debugging is enabled, a stackmap -
change abovementioned cases (integer div/rem,
unreachable) to use
::trapWithDebugStackMap -
propagate resulting bool-return-OOM changes in the usual way
-
change all other uses of ::trap to track its renaming; no functional change.
| Assignee | ||
Comment 9•8 months ago
|
||
| Assignee | ||
Comment 10•8 months ago
|
||
Comment 11•8 months ago
|
||
Comment 12•8 months ago
|
||
Comment 13•8 months ago
|
||
Verified bug as fixed on rev mozilla-central 20260303043313-961340a3993b.
Removing bugmon keyword as no further action possible. Please review the bug and re-add the keyword for further analysis.
Comment 14•8 months ago
|
||
The patch landed in nightly and beta is affected.
:jseward, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox149towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Updated•7 months ago
|
| Assignee | ||
Comment 15•7 months ago
|
||
I don't think this is worth uplifting because it requires the debugger to perform specific actions to lead to the UaF. I don't think web content could exploit this.
Updated•7 months ago
|
Updated•7 months ago
|
Updated•6 months ago
|
Updated•6 months ago
|
Comment 16•6 months ago
|
||
Updated•5 months ago
|
Comment 17•2 months ago
|
||
Comment 18•2 months ago
|
||
Comment 19•2 months ago
|
||
[task 2026-08-12T15:40:49.615+00:00] TEST-PASS | js/src/jit-test/tests/debug/bug2008675.js | Success (code 0, args "--blinterp-eager") [0.0 s]
[task 2026-08-12T15:40:49.615+00:00] @evaluate:22:13 ReferenceError: WebAssembly is not defined
[task 2026-08-12T15:40:49.615+00:00] Stack:
[task 2026-08-12T15:40:49.615+00:00] @@evaluate:22:13
[task 2026-08-12T15:40:49.615+00:00] @/builds/worker/checkouts/gecko/js/src/jit-test/tests/debug/bug2013619.js:12:3
[task 2026-08-12T15:40:49.615+00:00] Exit code: 3
[task 2026-08-12T15:40:49.615+00:00] FAIL - debug/bug2013619.js
[task 2026-08-12T15:40:49.615+00:00] TEST-UNEXPECTED-FAIL | js/src/jit-test/tests/debug/bug2013619.js | @evaluate:22:13 ReferenceError: WebAssembly is not defined (code 3, args "--ion-eager --ion-offthread-compile=off --ion-check-range-analysis --ion-extra-checks --no-sse3 --no-threads") [0.0 s]
[task 2026-08-12T15:40:49.615+00:00] INFO exit-status : 3
Comment 20•2 months ago
|
||
Comment 21•2 months ago
|
||
| bugherder | ||
Updated•2 months ago
|
Updated•2 months ago
|
| Assignee | ||
Updated•2 months ago
|
Updated•1 month ago
|
Description
•