WebAssembly: integer overflow in `Instance::init` on 32bit SpiderMonkey leads to heap overflow
Categories
(Core :: JavaScript: WebAssembly, defect, P2)
Tracking
()
People
(Reporter: nebusec.io, Assigned: jseward)
References
Details
(Keywords: csectype-bounds, reporter-external, sec-high, Whiteboard: [adv-main153+][adv-ESR140.13+])
Attachments
(1 file, 4 external files)
|
2.82 KB,
application/x-javascript
|
Details |
WebAssembly: integer overflow in Instance::init on 32bit SpiderMonkey leads to heap overflow
Description
At instantiation, Instance::init in js/src/wasm/WasmInstance.cpp trusts this baseline metadata count and performs a raw sizeof(gc::AllocSite) * allocSitesCount multiplication before js_malloc. It then initializes the original count of entries:
uint32_t allocSitesCount = codeTailMeta().numAllocSites;
if (allocSitesCount > 0) {
allocSites_ =
(gc::AllocSite*)js_malloc(sizeof(gc::AllocSite) * allocSitesCount);
if (!allocSites_) {
ReportOutOfMemory(cx);
return false;
}
for (uint32_t i = 0; i < allocSitesCount; ++i) {
new (&allocSites_[i]) gc::AllocSite();
allocSites_[i].initWasm(zone);
}
}
That layout makes sizeof(gc::AllocSite) 24 bytes on 32-bit targets. A baseline module with 178,956,971 dead-code allocation sites stores allocSitesCount = 178956971. The instantiation allocation computes 24 * 178956971 = 4294967304, which wraps to 8 in 32-bit size_t. The initialization loop still runs 178,956,971 iterations and constructs gc::AllocSite objects through the wrapped allocation, producing heap out-of-bounds writes during WebAssembly.Instance creation before any wasm function is called.
Reproduce
The js shell was compiled with the following mozconfig:
ac_add_options --enable-project=js
ac_add_options --target=i686-pc-linux-gnu
ac_add_options --enable-address-sanitizer
ac_add_options --disable-jemalloc
ac_add_options --disable-optimize
ac_add_options --enable-debug
ac_add_options --disable-install-strip
export CFLAGS="-m32"
export CXXFLAGS="-m32"
export LDFLAGS="-m32"
export HOST_CFLAGS=""
export HOST_CXXFLAGS=""
export HOST_LDFLAGS=""
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-js-asan-noopt-i686-pc-linux-gnu
Please run the proof-of-concept with:
obj-js-asan-noopt-i686-pc-linux-gnu/js ./asan_crash.js
The expected output:
totalSites=178956971
sitesPerFunc=1900000
funcCount=95
wasmBytes=715828669
wasmCompileMode=baseline
compiling
instantiating
=================================================================
==2221248==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf580af18 at pc 0x5b37c3bd bp 0xffd928a8 sp 0xffd928a0
WRITE of size 4 at 0xf580af18 thread T0
/audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/dist/bin/llvm-symbolizer: error: '[anon:js-executable-memory]': No such file or directory
#0 0x5b37c3bc in js::gc::AllocSite::AllocSite() /audit/ff/firefox/js/src/gc/Pretenuring.h:90:14
#1 0x5db3863f in js::wasm::Instance::init(JSContext*, JS::GCVector<JSObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::wasm::Val, 0u, js::SystemAllocPolicy> const&, JS::Handle<JS::GCVector<js::WasmMemoryObject*, 0u, js::SystemAllocPolicy>>, JS::GCVector<js::WasmGlobalObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmTagObject*, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<RefPtr<js::wasm::DataSegment const>, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::ModuleElemSegment, 0u, js::SystemAllocPolicy> const&) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmInstance.cpp:2647:29
#2 0x5dcdfe45 in js::WasmInstanceObject::create(JSContext*, RefPtr<js::wasm::Code const> const&, mozilla::Vector<RefPtr<js::wasm::DataSegment const>, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::ModuleElemSegment, 0u, js::SystemAllocPolicy> const&, unsigned int, JS::Handle<JS::GCVector<js::WasmMemoryObject*, 0u, js::SystemAllocPolicy>>, mozilla::Vector<RefPtr<js::wasm::Table>, 0u, js::SystemAllocPolicy>&&, JS::GCVector<JSObject*, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::GlobalDesc, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::wasm::Val, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmGlobalObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmTagObject*, 0u, js::SystemAllocPolicy> const&, JS::Handle<JSObject*>, std::unique_ptr<js::wasm::DebugState, JS::DeletePolicy<js::wasm::DebugState>>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmJS.cpp:2410:18
#3 0x5dccd459 in js::wasm::Module::instantiate(JSContext*, js::wasm::ImportValues&, JS::Handle<JSObject*>, JS::MutableHandle<js::WasmInstanceObject*>) const /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmModule.cpp:969:16
#4 0x5dce0abe in js::WasmInstanceObject::construct(JSContext*, unsigned int, JS::Value*) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmJS.cpp:2480:28
#5 0x5cb578ab in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:498:13
#6 0x5cb6242a in CallJSNativeConstructor(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:516:8
#7 0x5caecba0 in InternalConstruct(JSContext*, js::AnyConstructArgs const&, js::CallReason) /audit/ff/firefox/js/src/vm/Interpreter.cpp:722:14
#8 0x5caec0f4 in js::ConstructFromStack(JSContext*, JS::CallArgs const&, js::CallReason) /audit/ff/firefox/js/src/vm/Interpreter.cpp:769:10
#9 0x5b796f0c in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandle<JS::Value>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/jit/./../../../../js/src/jit/BaselineIC.cpp:1692:10
#10 0xe81f0b37 ([anon:js-executable-memory]+0x6b37)
0xf580af18 is located 0 bytes after 8-byte region [0xf580af10,0xf580af18)
allocated by thread T0 here:
#0 0x59ae0a7d in malloc /builds/worker/fetches/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:67:3
#1 0x59d13e9e in SystemMalloc::malloc(unsigned int) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/memory/build/./../../../memory/build/malloc_decls.h:58:1
#2 0x59d13d81 in DummyArenaAllocator<SystemMalloc>::moz_arena_malloc(unsigned int, unsigned int) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/memory/build/./../../../memory/build/malloc_decls.h:58:1
#3 0x59d13c2a in moz_arena_malloc /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/memory/build/./../../../memory/build/malloc_decls.h:209:1
#4 0x5db4b1ad in js_arena_malloc(unsigned int, unsigned int) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/dist/include/js/Utility.h:384:10
#5 0x5dafe970 in js_malloc(unsigned int) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/dist/include/js/Utility.h:388:10
#6 0x5db384b0 in js::wasm::Instance::init(JSContext*, JS::GCVector<JSObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::wasm::Val, 0u, js::SystemAllocPolicy> const&, JS::Handle<JS::GCVector<js::WasmMemoryObject*, 0u, js::SystemAllocPolicy>>, JS::GCVector<js::WasmGlobalObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmTagObject*, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<RefPtr<js::wasm::DataSegment const>, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::ModuleElemSegment, 0u, js::SystemAllocPolicy> const&) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmInstance.cpp:2641:25
#7 0x5dcdfe45 in js::WasmInstanceObject::create(JSContext*, RefPtr<js::wasm::Code const> const&, mozilla::Vector<RefPtr<js::wasm::DataSegment const>, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::ModuleElemSegment, 0u, js::SystemAllocPolicy> const&, unsigned int, JS::Handle<JS::GCVector<js::WasmMemoryObject*, 0u, js::SystemAllocPolicy>>, mozilla::Vector<RefPtr<js::wasm::Table>, 0u, js::SystemAllocPolicy>&&, JS::GCVector<JSObject*, 0u, js::SystemAllocPolicy> const&, mozilla::Vector<js::wasm::GlobalDesc, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::wasm::Val, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmGlobalObject*, 0u, js::SystemAllocPolicy> const&, JS::GCVector<js::WasmTagObject*, 0u, js::SystemAllocPolicy> const&, JS::Handle<JSObject*>, std::unique_ptr<js::wasm::DebugState, JS::DeletePolicy<js::wasm::DebugState>>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmJS.cpp:2410:18
#8 0x5dccd459 in js::wasm::Module::instantiate(JSContext*, js::wasm::ImportValues&, JS::Handle<JSObject*>, JS::MutableHandle<js::WasmInstanceObject*>) const /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmModule.cpp:969:16
#9 0x5dce0abe in js::WasmInstanceObject::construct(JSContext*, unsigned int, JS::Value*) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/wasm/./../../../../js/src/wasm/WasmJS.cpp:2480:28
#10 0x5cb578ab in CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), js::CallReason, JS::CallArgs const&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:498:13
#11 0x5cb6242a in CallJSNativeConstructor(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:516:8
#12 0x5caecba0 in InternalConstruct(JSContext*, js::AnyConstructArgs const&, js::CallReason) /audit/ff/firefox/js/src/vm/Interpreter.cpp:722:14
#13 0x5caec0f4 in js::ConstructFromStack(JSContext*, JS::CallArgs const&, js::CallReason) /audit/ff/firefox/js/src/vm/Interpreter.cpp:769:10
#14 0x5b796f0c in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandle<JS::Value>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/jit/./../../../../js/src/jit/BaselineIC.cpp:1692:10
#15 0xe81f0b37 ([anon:js-executable-memory]+0x6b37)
#16 0xe821a537 ([anon:js-executable-memory]+0x537)
#17 0xe821e8ab ([anon:js-executable-memory]+0x48ab)
#18 0xe81ea524 ([anon:js-executable-memory]+0x524)
#19 0x5c189b81 in EnterBaseline(JSContext*, EnterJitData&) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/jit/./../../../../js/src/jit/BaselineJIT.cpp:146:5
#20 0x5c188f39 in js::jit::EnterBaselineInterpreterAtBranch(JSContext*, js::InterpreterFrame*, unsigned char*) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/jit/./../../../../js/src/jit/BaselineJIT.cpp:201:26
#21 0x5caf8a2b in js::Interpret(JSContext*, js::RunState&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:2040:17
#22 0x5cae9d92 in MaybeEnterInterpreterTrampoline(JSContext*, js::RunState&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:392:10
#23 0x5cae9619 in js::RunScript(JSContext*, js::RunState&) /audit/ff/firefox/js/src/vm/Interpreter.cpp:468:13
#24 0x5caee77c in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /audit/ff/firefox/js/src/vm/Interpreter.cpp:858:10
#25 0x5caef00d in js::Execute(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::MutableHandle<JS::Value>) /audit/ff/firefox/js/src/vm/Interpreter.cpp:888:10
#26 0x5d2a5e81 in ExecuteScript(JSContext*, JS::Handle<JSObject*>, JS::Handle<JSScript*>, JS::MutableHandle<JS::Value>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/vm/./../../../../js/src/vm/CompilationAndEvaluation.cpp:552:10
#27 0x5d2a6163 in JS_ExecuteScript(JSContext*, JS::Handle<JSScript*>) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/vm/./../../../../js/src/vm/CompilationAndEvaluation.cpp:576:10
#28 0x59bddd93 in RunFile(JSContext*, char const*, _IO_FILE*, CompileUtf8, bool, bool) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/shell/./../../../../js/src/shell/js.cpp:1413:10
#29 0x59bdd13d in Process(JSContext*, char const*, bool, FileKind) /audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/js/src/shell/./../../../../js/src/shell/js.cpp:2205:14
SUMMARY: AddressSanitizer: heap-buffer-overflow /audit/ff/firefox/js/src/gc/Pretenuring.h:90:14 in js::gc::AllocSite::AllocSite()
Shadow bytes around the buggy address:
0xf580ac80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580ad00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580ad80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580ae00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580ae80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fd fd
=>0xf580af00: fa fa 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
0xf580af80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580b000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580b080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580b100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf580b180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2221248==ABORTING
Comment 1•4 months ago
|
||
Not confirmed, but likely sec-high if it affects Firefox and not just the shell. Eyeballing the POC nothing shell-only jumped out at me, but in future js shell testing please use the --fuzzing-safe option. That disables many shell-only developer functions that lead fuzzers down bind alleys creating invalid states that aren't possible in a Firefox release.
Updated•4 months ago
|
| Reporter | ||
Comment 2•4 months ago
|
||
I can confirmed that works with --fuzzing-safe as well. To be clear the current poc only works on 32bit build. The commit hash is 6a252a86c5bb0fdd7a197d1c244f8a28e0827b3a the mirror https://github.com/mozilla-firefox/firefox
/audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/dist/bin/js --fuzzing-safe ./asan_crash.js
totalSites=178956971
sitesPerFunc=1900000
funcCount=95
wasmBytes=715828669
wasmCompileMode=baseline+ion
compiling
instantiating
=================================================================
==1247078==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf5806e58 at pc 0x5b44ce8d bp 0xffc3bf28 sp 0xffc3bf20
WRITE of size 4 at 0xf5806e58 thread T0
/audit/ff/firefox/obj-js-asan-noopt-i686-pc-linux-gnu/dist/bin/llvm-symbolizer: error: '[anon:js-executable-memory]': No such file or directory
...
SUMMARY: AddressSanitizer: heap-buffer-overflow /audit/ff/firefox/js/src/gc/Pretenuring.h:90:14 in js::gc::AllocSite::AllocSite()
Shadow bytes around the buggy address:
0xf5806b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5806c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5806c80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5806d00: fa fa fd fd fa fa 04 fa fa fa fd fd fa fa fd fa
0xf5806d80: fa fa fd fd fa fa 00 00 fa fa fd fa fa fa fd fd
=>0xf5806e00: fa fa fd fd fa fa 00 00 fa fa 00[fa]fa fa fa fa
0xf5806e80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5806f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5806f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5807000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0xf5807080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1247078==ABORTING
Comment 3•4 months ago
|
||
This looks real, and I don't think requires anything fuzzing-unsafe. I'm impressed the code doesn't OOM on 32-bit with that many allocation instructions.
| Assignee | ||
Updated•4 months ago
|
| Assignee | ||
Comment 4•3 months ago
|
||
| Assignee | ||
Comment 5•3 months ago
|
||
Comment 8•3 months ago
|
||
Comment 9•3 months ago
|
||
Authored by https://github.com/julian-seward1
https://github.com/mozilla/enterprise-firefox/commit/20715139c2a9526c0be17ea1c0c4102218c15177
[enterprise-main] Bug 2051015. r=jpages.
Comment 10•3 months ago
|
||
Please add uplift requests for beta and ESR140
| Assignee | ||
Comment 11•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D310841
Updated•3 months ago
|
Comment 12•3 months ago
|
||
firefox-beta Uplift Approval Request
- User impact if declined/Reason for urgency: On 32-bit targets only: heap OOB write caused by vanilla wasm input. Whether the written data is controllable by the attacker is unclear; I suspect not.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: low
- Explanation of risk level: Very simple patch, that merely adds overflow checking for a previously unchecked u32 x u32 multiply.
- String changes made/needed?: none
- Is Android affected?: yes
Comment 13•3 months ago
|
||
firefox-esr140 Uplift Approval Request
- User impact if declined/Reason for urgency: On 32-bit targets only: heap OOB write caused by vanilla wasm input. Whether the written data is controllable by the attacker is unclear; I suspect not.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing:
- Risk associated with taking this patch: low
- Explanation of risk level: Very simple patch, that merely adds overflow checking for a previously unchecked u32 x u32 multiply.
- String changes made/needed?: none
- Is Android affected?: yes
| Assignee | ||
Comment 14•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D310841
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Comment 15•3 months ago
|
||
| uplift | ||
Comment 16•3 months ago
|
||
| uplift | ||
Updated•3 months ago
|
| Assignee | ||
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Updated•1 month ago
|
Updated•1 month ago
|
Comment 19•7 days ago
|
||
Unfortunately your bug report fell within the excluded collision window of our automated scans and is not eligible for a bounty
https://github.com/mozilla/bedrock/blob/eaec5cd9e1a0be974702e3d4f153ea9c23c673b8/bedrock/security/templates/security/bug-bounty.html#L27
Description
•