Open Bug 2058674 Opened 3 months ago Updated 2 months ago

Assertion failure: wasm::CodeCachingAvailable(cx) [@ WasmCompileAndSerialize] — JS shell drops -P prefs when spawning the --wasm-compile-and-serialize child process

Categories

(Core :: JavaScript: WebAssembly, defect, P3)

defect

Tracking

()

UNCONFIRMED

People

(Reporter: bugmon, Unassigned)

References

(Blocks 1 open bug)

Details

(Keywords: ai-involved, assertion, testcase)

Attachments

(4 files)

The SpiderMonkey shell's fuzzing-safe testing function wasmCompileInSeparateProcess() re-executes the shell binary with --wasm-compile-and-serialize to compile a wasm module out of process. The parent gates the call on wasm::CodeCachingAvailable(cx) (js/src/shell/js.cpp:7202), which since lazy tiering became the default returns false unless the shell is run with -P wasm_lazy_tiering=false (js/src/wasm/WasmFeatures.cpp:304; pref default true per modules/libpref/init/StaticPrefList.yaml:10040-10042). When spawning the child, however, the shell only forwards the hardcoded sCompilerProcessFlags list — CPU-feature flags, --test-wasm-await-tier2, and --wasm-compiler= (js/src/shell/js.cpp:7033-7038, 13815-13850). -P prefs are not forwarded. The child therefore runs with lazy tiering enabled, its CodeCachingAvailable check is false, and it immediately fails MOZ_ASSERT(wasm::CodeCachingAvailable(cx)) in WasmCompileAndSerialize (js/src/shell/js.cpp:7151), crashing via MOZ_CrashSequence (reported by UBSan as a null write).

Reproduced independently on a debug shell built from mozconfig.js.linux.debug: running the testcase under --fuzzing-safe -P wasm_lazy_tiering=false yields the identical assertion and stack (WasmCompileAndSerialize <- main) as the researcher's crash data. Without the extra pref the parent gate refuses with 'WebAssembly caching not supported' and no crash occurs, confirming the parent/child pref-mismatch mechanism.

Impact assessment: the crash is a controlled diagnostic assertion in the shell test harness, reachable only through a shell-only testing function; it is not reachable from web content and does not demonstrate memory corruption. Its relevance beyond a harness defect is that in release builds the assert compiles out and the child would run wasm::CompileAndSerialize under a configuration the engine declares unsupported for serialization — the invariants it depends on are enforced downstream only by MOZ_RELEASE_ASSERT(item->inlinedCallerOffsetsMap_.empty()) in js/src/wasm/WasmSerialize.cpp:958 and :982.

Build Info

Affected Code

File: js/src/shell/js.cpp, line 7150-7151

static bool WasmCompileAndSerialize(JSContext* cx) {
  MOZ_ASSERT(wasm::CodeCachingAvailable(cx));   // <-- child crashes here: pref not propagated

File: js/src/shell/js.cpp, line 7200-7205

static bool WasmCompileInSeparateProcess(JSContext* cx, unsigned argc,
                                         Value* vp) {
  if (!wasm::CodeCachingAvailable(cx)) {   // parent gate passes with -P wasm_lazy_tiering=false
    JS_ReportErrorASCII(cx, "WebAssembly caching not supported");
    return false;
  }

File: js/src/shell/js.cpp, line 13815-13826

  // Also the following are to be propagated.
  const char* to_propagate[] = {
      // Compiler selection options
      "--test-wasm-await-tier2",
  };
  for (const char* p : to_propagate) {
    if (op.getBoolOption(p + 2 /* 2 => skip the leading '--' */)) {
      if (!sCompilerProcessFlags.append(p)) {
        return false;
      }
    }
  }
  // (only --wasm-compiler= is additionally reconstituted below; -P prefs are never appended)

File: js/src/wasm/WasmFeatures.cpp, line 296-314

bool wasm::CodeCachingAvailable(JSContext* cx) {
  ...
  // TODO(bug 1913109): lazy tiering doesn't support serialization
  if (JS::Prefs::wasm_lazy_tiering()) {   // default true -> false in the child
    return false;
  }
  return StreamingCompilationAvailable(cx) && IonAvailable(cx);
}

File: js/src/wasm/WasmSerialize.cpp, line 955-959

  MOZ_TRY(CodePodVector(coder, &item->pcOffsets_));
  MOZ_TRY(CodePodVector(coder, &item->bytecodeOffsets_));
  // Inlining requires lazy tiering, which does not support serialization yet.
  MOZ_RELEASE_ASSERT(item->inlinedCallerOffsetsMap_.empty());   // same assert in CodeCallSites at :982

The child-side assertion, the parent-side availability gate, the flag-propagation allowlist that omits -P prefs, the pref-dependent availability check, and the downstream serialization release-asserts the availability gate protects.

Exploit Chain

  1. Run the SpiderMonkey debug shell with --fuzzing-safe -P wasm_lazy_tiering=false, making wasm::CodeCachingAvailable(cx) true in the parent process (js/src/shell/js.cpp:7202 gate passes).
  2. Script calls the fuzzing-safe testing function wasmCompileInSeparateProcess(bytecode).
  3. CompileAndSerializeInSeparateProcess re-executes the same shell binary with --wasm-compile-and-serialize, forwarding only sCompilerProcessFlags (CPU-feature flags, --test-wasm-await-tier2, --wasm-compiler=); the -P wasm_lazy_tiering=false pref is dropped (js/src/shell/js.cpp:7033-7038, 13815-13850).
  4. The child runs with the default wasm_lazy_tiering=true, so wasm::CodeCachingAvailable(cx) is false there (js/src/wasm/WasmFeatures.cpp:304).
  5. The child hits MOZ_ASSERT(wasm::CodeCachingAvailable(cx)) in WasmCompileAndSerialize (js/src/shell/js.cpp:7151) and crashes via MOZ_CrashSequence; UBSan reports the deliberate null write. In a release build the assert compiles out and the child proceeds to serialize under a configuration the engine marks unsupported, guarded downstream only by the MOZ_RELEASE_ASSERTs in js/src/wasm/WasmSerialize.cpp:958/982.

Steps to Reproduce

  1. Build the SpiderMonkey debug shell: build_firefox with mozconfig /app/mozconfigs/mozconfig.js.linux.debug (build_dir /firefox/obj-js-debug).
  2. Run the testcase via js_shell_evaluator with js_binary /firefox/obj-js-debug/dist/bin/js and flags ["-P", "wasm_lazy_tiering=false"] (the evaluator always adds --fuzzing-safe).
  3. Testcase: const bin = wasmTextToBinary((module (memory 1) (func (export "run") (param i32) (result i32) (i32.add (local.get 0) (i32.const 1))))); print("caching: " + wasmCachingEnabled()); const mod = wasmCompileInSeparateProcess(bin); print(wasmLoadedFromCache(mod));
  4. Observe crashed: true with 'Assertion failure: wasm::CodeCachingAvailable(cx), at js/src/shell/js.cpp:7151' from the spawned --wasm-compile-and-serialize child on stderr. Without the -P flag, the call instead fails cleanly with 'WebAssembly caching not supported' and no crash.

Security Impact

  • Severity: Low
  • Attacker capability: Deterministic assertion crash of the SpiderMonkey shell's out-of-process wasm compiler child. The trigger is a shell-only testing function (wasmCompileInSeparateProcess); no memory corruption was demonstrated, and no path from web content exists. In release shell builds the compiled-out assert means the child serializes wasm code under a configuration the engine declares unsupported for serialization, with the WasmSerialize.cpp MOZ_RELEASE_ASSERTs (lines 958, 982) as the remaining guard.
  • Preconditions: SpiderMonkey shell with testing functions available (runs under --fuzzing-safe); shell invoked with -P wasm_lazy_tiering=false so the parent-side CodeCachingAvailable gate passes while the pref is dropped for the child.

ASAN Report

[954291] Assertion failure: wasm::CodeCachingAvailable(cx), at js/src/shell/js.cpp:7151
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==954291==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5c920be6418f bp 0x7ffe7ab6a5d0 sp 0x7ffe7ab6a550 T954291)
==954291==The signal is caused by a WRITE memory access.
==954291==Hint: address points to the zero page.
    #0 0x5c920be6418f in MOZ_CrashSequence(void*, long) /firefox/obj-js-debug/dist/include/mozilla/Assertions.h:261:3
    #1 0x5c920be6418f in WasmCompileAndSerialize(JSContext*) /firefox/obj-js-debug/js/src/shell/./../../../../js/src/shell/js.cpp:7151:3
    #2 0x5c920be5d5a3 in main /firefox/obj-js-debug/js/src/shell/./../../../../js/src/shell/js.cpp:12799:10
    #3 0x7de815cfb1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9)
    #4 0x7de815cfb28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a)
    #5 0x5c920be23578 in _start (/firefox/obj-js-debug/dist/bin/js+0x270e578)
SUMMARY: UndefinedBehaviorSanitizer: SEGV /firefox/obj-js-debug/dist/include/mozilla/Assertions.h:261:3 in MOZ_CrashSequence(void*, long)
==954291==ABORTING
Attached file testcase.js —
Attached file NOTES.md —
Attached file crash_stack.txt —
Attached patch iter_3.patch — — Splinter Review

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 crash is real (confirmed by ASAN), but the root cause analysis and suggested fix may contain errors.

WARNING: A blocking issue was auto-patched in the source used for this analysis. Please flag if you believe the bug was introduced by that patch.

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

Attachment

General

Created:
Updated:
Size: