Closed Bug 2040428 Opened 5 months ago Closed 5 months ago

SpiderMonkey source-phase wasm ESM leaks heap bytes via non-NUL-terminated names in LinkError.message

Categories

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

Firefox 152
defect

Tracking

()

RESOLVED FIXED
153 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox151 --- disabled
firefox152 --- disabled
firefox153 --- fixed

People

(Reporter: s3zer0, Assigned: dminor)

References

(Blocks 2 open bugs)

Details

(4 keywords)

Attachments

(1 file, 1 external file)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36

Steps to reproduce:

Tested environment:

  • Source tree: GitHub mirror https://github.com/mozilla-firefox/firefox.git
  • Commit: cc4a6379e9769a405892373ad8aabcfea975ae06
  • Firefox build: 152.0a1
  • Build ID: 20260519005927
  • User agent:
    Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0

I reproduced this with a local current-tree Firefox build that has source-phase imports enabled.

  1. Build Firefox from current mozilla-central with source-phase imports enabled:
. ./browser/config/mozconfig
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-browser-opt
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-source-phase-imports
MOZCONFIG=/path/to/mozconfig ./mach build
  1. Create a fresh profile or start the browser with these prefs enabled:
user_pref("javascript.options.experimental.source_phase_imports", true);
user_pref("javascript.options.experimental.wasm_esm_integration", true);
  1. Serve the attached PoC folder over HTTP. The server included in the PoC serves .wasm as application/wasm.
cd /path/to/poc-folder
node server.mjs
  1. Open the PoC page in the Firefox build from step 1:
http://127.0.0.1:8787/

The page performs:

await import.source("./leak.wasm");

The attached leak.wasm contains a reserved source-phase wasm import module name beginning with wasm-js:. The name is valid length-tracked wasm UTF-8, but it is not NUL-terminated in memory. The wasm also contains same-size non-NUL Q import names so that adjacent heap bytes are easy to identify if they are over-read.

Actual results:

The dynamic source-phase import rejects with a LinkError, but the LinkError.message string contains bytes beyond the end of the 64-byte wasm import module name.

In my current-tree Firefox browser validation, the page printed:

{
  "ok": false,
  "file": "./leak.wasm",
  "errorName": "LinkError",
  "messageType": "string",
  "length": 1441,
  "expectedLength": 97,
  "excess": 1344,
  "markerSeen": true,
  "tailPreview": "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ..."
}

The expected message length is the fixed diagnostic text plus the 64-byte wasm name. The extra 1344 bytes are read from memory after the non-NUL-terminated wasm name and are exposed to JavaScript through LinkError.message. The markerSeen=true result shows that attacker-controlled adjacent heap bytes are included in the message.

The browser verification also showed that leak.wasm was fetched by the module loader:

{
  "name": "http://127.0.0.1:8787/leak.wasm",
  "initiatorType": "script",
  "transferSize": 4803
}

I also confirmed the same root cause in an ASan SpiderMonkey shell build: reserved import module name, reserved import field name, and reserved export name diagnostics all trigger ASan heap-buffer-overflow reads in the same error-reporting path.

Expected results:

Firefox should reject the wasm module with a LinkError, but the diagnostic should be formatted using the wasm name's explicit length.

LinkError.message should contain only the intended diagnostic and the actual 64-byte wasm name. It should not call strlen on a length-tracked wasm name, should not read past the wasm name allocation, and should not expose adjacent heap bytes to JavaScript.

Group: firefox-core-security → javascript-core-security
Component: Untriaged → JavaScript: WebAssembly
Product: Firefox → Core
Flags: sec-bounty?

We also found this internally.

The bug has a release status flag that shows some version of Firefox is affected, thus it will be considered confirmed.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Severity: -- → S2
Priority: -- → P2
Assignee: nobody → dminor
Attached file (secure) —

Since this is an unsupported configuration (preffed off by default, Nightly only), can I go ahead and land the fix with a test case attached? Or does this need security review and tests in a separate bug?

(In reply to Dan Minor [:dminor] from comment #4)

Since this is an unsupported configuration (preffed off by default, Nightly only), can I go ahead and land the fix with a test case attached? Or does this need security review and tests in a separate bug?

It's fine to land this without sec-approval, including tests. In general no sec-approval is needed for security bugs that only affect Nightly and in this case it's even preffed off there.

Group: javascript-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 5 months ago
Resolution: --- → FIXED
Target Milestone: --- → 153 Branch
Duplicate of this bug: 2042732
Duplicate of this bug: 2042748

Experimental features and non-default preference settings are out of scope for our bug bounty program.

Flags: sec-bounty? → sec-bounty-
QA Whiteboard: [sec] [qa-triage-done-c154/b153]
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: