SpiderMonkey source-phase wasm ESM leaks heap bytes via non-NUL-terminated names in LinkError.message
Categories
(Core :: JavaScript: WebAssembly, defect, P2)
Tracking
()
| 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.
- 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
- 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);
- Serve the attached PoC folder over HTTP. The server included in the PoC serves
.wasmasapplication/wasm.
cd /path/to/poc-folder
node server.mjs
- 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.
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
| Reporter | ||
Updated•5 months ago
|
Comment 1•5 months ago
|
||
We also found this internally.
Comment 2•5 months ago
|
||
The bug has a release status flag that shows some version of Firefox is affected, thus it will be considered confirmed.
Updated•5 months ago
|
| Assignee | ||
Updated•5 months ago
|
| Assignee | ||
Comment 3•5 months ago
|
||
| Assignee | ||
Comment 4•5 months ago
|
||
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?
Comment 5•5 months ago
|
||
(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.
Comment 7•5 months ago
|
||
Comment 10•5 months ago
|
||
Experimental features and non-default preference settings are out of scope for our bug bounty program.
Updated•4 months ago
|
Updated•1 month ago
|
Description
•