Open Bug 2068423 Opened 2 months ago Updated 2 months ago

Assertion failure: canSkip == true in [@ js::ExtractAwaitValue] when a Debugger onStep hook mutates the awaited promise between CanSkipAwait and MaybeExtractAwaitValue

Categories

(Core :: JavaScript: Debugger API, defect, P3)

defect

Tracking

()

UNCONFIRMED

People

(Reporter: bugmon, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: assertion, testcase)

Attachments

(4 files)

BytecodeEmitter::emitAwaitableExpr emits JSOp::CanSkipAwait followed by JSOp::MaybeExtractAwaitValue with no re-validation between them, and the interpreter's interrupt path runs DebugAPI::onSingleStep before dispatching the second op (Interpreter.cpp:1994), so a Debugger.Frame.onStep hook can add an own property to the awaited promise and make IsPromiseWithDefaultProperties false while the true pushed by CanSkipAwait is still on the operand stack. MaybeExtractAwaitValue consumes that stale boolean and calls js::ExtractAwaitValue, whose DEBUG re-check fails MOZ_ASSERT(canSkip == true) at js/src/builtin/Promise.cpp:8316 (reachable from a plain js --fuzzing-safe testcase); in a non-debug build the await instead completes synchronously from promise->value() after its precondition was invalidated.

Exploit Chain

  1. Create a debuggee global with newGlobal({newCompartment: true}) and attach a Debugger; set onEnterFrame so that frames for async function f get an onStep hook.
  2. In the debuggee, create a fulfilled promise p = Promise.resolve(1) and run async function f() { await null; await null; await p; }. The leading awaits ensure await p executes in a resumed generator activation that is the sole activation, satisfying CanSkipAwait's activation checks (Promise.cpp:8235-8246).
  3. Sweep the onStep count k so that on one attempt the hook fires exactly between JSOp::CanSkipAwait (which pushed true) and JSOp::MaybeExtractAwaitValue.
  4. The hook executes g.p.then = 1, giving p an own property so promise->empty() is now false.
  5. MaybeExtractAwaitValue reads the stale true and calls js::ExtractAwaitValue; its DEBUG re-check computes canSkip == false and MOZ_ASSERT(canSkip == true) fails at Promise.cpp:8316. In a non-debug build the value is extracted synchronously despite the invalidated precondition.

Affected Code

Steps to Reproduce

  1. Build the SpiderMonkey debug shell with /app/mozconfigs/mozconfig.js.linux.debug on an unmodified tree.
  2. Run await_skip.js through js_shell_evaluator (equivalently: js --fuzzing-safe await_skip.js). No prefs, extra flags, or patches.
  3. Observe: Assertion failure: canSkip == true, at js/src/builtin/Promise.cpp:8316 in js::ExtractAwaitValue, called from js::Interpret (Interpreter.cpp:3787) inside AsyncFunctionResume / RunShellJobs.

Security Impact

  • Severity: Low
  • Attacker capability: Triggering the assertion requires the Debugger API, which the shell exposes under --fuzzing-safe but which is not available to web content. In debug builds the result is a deterministic assertion failure (process abort). In the non-debug ASAN shell the same testcase completes with no sanitizer report; the observed consequence is that the await resolves synchronously from promise->value() after the fast-path precondition has been invalidated. No memory corruption was demonstrated.
  • Preconditions: Ability to run JS with access to the Debugger API (SpiderMonkey shell or privileged/devtools code) against the async function being stepped; the awaited value must be a fulfilled native promise with default properties at the time CanSkipAwait executes.

The full analysis report is attached (analysis.md).

Attached patch fix.patch — — Splinter Review
Attached file crash_stack.txt —
Attached file await_skip.js —
Attached file analysis.md —

This bug was filed automatically by an AI-powered security testing tool. The analysis, testcase, and any suggested patches were generated by an AI model and have NOT been verified by a human.

Please review all details carefully before triaging. The issue was confirmed by an independent verifier, but for non-ASAN issues, the verifier has been made less strict. Additionally, the root cause analysis and suggested fix may contain errors.

Group: core-security → javascript-core-security
Component: JavaScript Engine → JavaScript: Debugger API
Blocks: sm-security
Severity: -- → S3
Priority: -- → P3
Group: javascript-core-security
Keywords: sec-other
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: