Closed Bug 1645610 Opened 7 months ago Closed 7 months ago

WebAssembly.Module.exports out-of-bounds read

Categories

(Core :: Javascript: WebAssembly, defect, P1)

defect

Tracking

()

RESOLVED FIXED
mozilla79
Tracking Status
firefox-esr68 --- disabled
firefox-esr78 --- disabled
firefox77 --- disabled
firefox78 --- disabled
firefox79 --- fixed

People

(Reporter: guidovranken, Assigned: lth)

References

Details

(Keywords: sec-other, Whiteboard: [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage])

Crash Data

Attachments

(1 file)

Compile recent Spidermonkey (I've personally confirmed this with commits dff6c8722db09c334809a96c2f90e95ea44360f5, 20ad814e4cc3d5eb3e33ccec6ed2b17255205aca) using these commands:

../configure --disable-jemalloc --disable-tests --enable-address-sanitizer && make -j12

Then run the following command:

dist/bin/js -e "WebAssembly.Module.exports(new WebAssembly.Module(new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,1124121,,,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11])));"

Result:

=================================================================
==28275==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61000001e400 at pc 0x555edcc48fc9 bp 0x7fff7a754870 sp 0x7fff7a754868
READ of size 8 at 0x61000001e400 thread T0
#0 0x555edcc48fc8 in mozilla::Vector<js::wasm::ValType, 16ul, js::SystemAllocPolicy>::begin() const /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/mozilla/Vector.h:466:12
#1 0x555edcc48fc8 in FuncTypeToString(JSContext*, js::wasm::FuncType const&) /mnt/2tb/spidermonkey/mozilla-central/js/src/wasm/WasmJS.cpp:1028:20
#2 0x555edcc43629 in js::WasmModuleObject::exports(JSContext*, unsigned int, JS::Value*) /mnt/2tb/spidermonkey/mozilla-central/js/src/wasm/WasmJS.cpp:1196:11
#3 0x555eda96c248 in js::InternalCallOrConstruct(JSContext*, JS::CallArgs const&, js::MaybeConstruct, js::CallReason) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:486:13
#4 0x555eda9563c1 in InternalCall(JSContext*, js::AnyInvokeArgs const&, js::CallReason) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:641:10
#5 0x555eda9563c1 in js::CallFromStack(JSContext*, JS::CallArgs const&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:645:10
#6 0x555eda9563c1 in Interpret(JSContext*, js::RunState&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:3300:16
#7 0x555eda93add1 in js::RunScript(JSContext*, js::RunState&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:458:10
#8 0x555eda9726b3 in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::Handle<JS::Value>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:833:13
#9 0x555edacfc2ae in bool EvaluateSourceBuffer<mozilla::Utf8Unit>(JSContext*, js::ScopeKind, JS::Handle<JSObject*>, JS::ReadOnlyCompileOptions const&, JS::SourceText<mozilla::Utf8Unit>&, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/CompilationAndEvaluation.cpp:497:10
#10 0x555edacfc2ae in JS::Evaluate(JSContext*, JS::ReadOnlyCompileOptions const&, JS::SourceText<mozilla::Utf8Unit>&, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/CompilationAndEvaluation.cpp:505:10
#11 0x555eda780ba4 in Shell(JSContext*, js::cli::OptionParser*, char**) /mnt/2tb/spidermonkey/mozilla-central/js/src/shell/js.cpp:10163:12
#12 0x555eda773cd1 in main /mnt/2tb/spidermonkey/mozilla-central/js/src/shell/js.cpp:11588:12
#13 0x7f027de5bb96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
#14 0x555eda6af029 in _start (/mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/bin/js+0x1d47029)

0x61000001e400 is located 0 bytes to the right of 192-byte region [0x61000001e340,0x61000001e400)
allocated by thread T0 here:
#0 0x555eda7279ad in __interceptor_malloc /builds/worker/fetches/llvm-project/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:145:3
#1 0x555edcb20531 in js_arena_malloc(unsigned long, unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/js/Utility.h:384:10
#2 0x555edcb20531 in js::wasm::FuncExport* js_pod_arena_malloc<js::wasm::FuncExport>(unsigned long, unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/js/Utility.h:592:26
#3 0x555edcb20531 in js::wasm::FuncExport* js::AllocPolicyBase::maybe_pod_arena_malloc<js::wasm::FuncExport>(unsigned long, unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/js/AllocPolicy.h:31:12
#4 0x555edcb20531 in js::wasm::FuncExport* js::AllocPolicyBase::pod_arena_malloc<js::wasm::FuncExport>(unsigned long, unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/js/AllocPolicy.h:44:12
#5 0x555edcb20531 in js::wasm::FuncExport* js::AllocPolicyBase::pod_malloc<js::wasm::FuncExport>(unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/js/AllocPolicy.h:70:12
#6 0x555edcb20531 in mozilla::Vector<js::wasm::FuncExport, 0ul, js::SystemAllocPolicy>::convertToHeapStorage(unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/mozilla/Vector.h:917:30
#7 0x555edcb455a8 in mozilla::Vector<js::wasm::FuncExport, 0ul, js::SystemAllocPolicy>::reserve(unsigned long) /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/mozilla/Vector.h:1059:9
#8 0x555edcb455a8 in js::wasm::ModuleGenerator::init(js::wasm::Metadata*) /mnt/2tb/spidermonkey/mozilla-central/js/src/wasm/WasmGenerator.cpp:410:35
#9 0x555edca2dc4a in js::wasm::CompileBuffer(js::wasm::CompileArgs const&, js::wasm::ShareableBytes const&, mozilla::UniquePtr<char [], JS::FreePolicy>, mozilla::Vector<mozilla::UniquePtr<char [], JS::FreePolicy>, 0ul, js::SystemAllocPolicy>, JS::OptimizedEncodingListener*) /mnt/2tb/spidermonkey/mozilla-central/js/src/wasm/WasmCompile.cpp:580:11
#10 0x555edcc49b8b in js::WasmModuleObject::construct(JSContext*, unsigned int, JS::Value*) /mnt/2tb/spidermonkey/mozilla-central/js/src/wasm/WasmJS.cpp:1397:7
#11 0x555eda970512 in InternalConstruct(JSContext*, js::AnyConstructArgs const&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:486:13
#12 0x555eda95667c in js::ConstructFromStack(JSContext*, JS::CallArgs const&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:731:10
#13 0x555eda95667c in Interpret(JSContext*, js::RunState&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:3290:16
#14 0x555eda93add1 in js::RunScript(JSContext*, js::RunState&) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:458:10
#15 0x555eda9726b3 in js::ExecuteKernel(JSContext*, JS::Handle<JSScript*>, JS::Handle<JSObject*>, JS::Handle<JS::Value>, js::AbstractFramePtr, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/Interpreter.cpp:833:13
#16 0x555edacfc2ae in bool EvaluateSourceBuffer<mozilla::Utf8Unit>(JSContext*, js::ScopeKind, JS::Handle<JSObject*>, JS::ReadOnlyCompileOptions const&, JS::SourceText<mozilla::Utf8Unit>&, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/CompilationAndEvaluation.cpp:497:10
#17 0x555edacfc2ae in JS::Evaluate(JSContext*, JS::ReadOnlyCompileOptions const&, JS::SourceText<mozilla::Utf8Unit>&, JS::MutableHandle<JS::Value>) /mnt/2tb/spidermonkey/mozilla-central/js/src/vm/CompilationAndEvaluation.cpp:505:10
#18 0x555eda780ba4 in Shell(JSContext*, js::cli::OptionParser*, char**) /mnt/2tb/spidermonkey/mozilla-central/js/src/shell/js.cpp:10163:12
#19 0x555eda773cd1 in main /mnt/2tb/spidermonkey/mozilla-central/js/src/shell/js.cpp:11588:12
#20 0x7f027de5bb96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310

SUMMARY: AddressSanitizer: heap-buffer-overflow /mnt/2tb/spidermonkey/mozilla-central/js/src/build-dbg/dist/include/mozilla/Vector.h:466:12 in mozilla::Vector<js::wasm::ValType, 16ul, js::SystemAllocPolicy>::begin() const
Shadow bytes around the buggy address:
0x0c207fffbc30: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
0x0c207fffbc40: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x0c207fffbc50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c207fffbc60: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x0c207fffbc70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c207fffbc80:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c207fffbc90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c207fffbca0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c207fffbcb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c207fffbcc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c207fffbcd0: 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
Shadow gap: cc
==28275==ABORTING

Flags: sec-bounty?
Group: firefox-core-security → javascript-core-security
Component: Security → Javascript: WebAssembly
Product: Firefox → Core
Flags: needinfo?(lhansen)
Assignee: nobody → lhansen
Severity: -- → S3
Status: UNCONFIRMED → ASSIGNED
Type: task → defect
Ever confirmed: true
Flags: needinfo?(lhansen)
Priority: -- → P1

Guido, can you confirm that there is a segment in the test case byte array that should read '1124121,,,0' and that that's not a copy/paste error that came in when you filed the bug? Thanks.

Flags: needinfo?(guidovranken)

Also, I can't find the changesets you quote on mozilla-central. Are they from a different branch / repository?

Ah. Needs to be invoked with --fuzzing-safe. Can repro on m-c tip as of this morning: 535633:c68fe15a81fc

Flags: needinfo?(guidovranken)

The weird array contents is intentional and is the result of fuzzing (probably mutated from a Dharma testcase).

When I reported this (and reproduced) this I didn't need to invoke --fuzzing-safe. I could also reproduce a segfault using default build commands and without AddressSanitizer.

This just needs a regular debug build to reproduce (not asan), at which point we get an OOB array-reference assertion.

wasm2wat thinks the module looks like this:

(module
  (type (;0;) (func (result i32)))
  (func (;0;) (type 0) (result i32)
    i32.const 42)
  (table (;0;) 0 funcref)
  (memory (;0;) 1)
  (export "memo\19\00" (func 0))
  (export "main" (func 0)))

The problem only occurs under --fuzzing-safe because there's code guarded on that flag that indexes into an array of function exports that assumes a function can only be exported once:

  size_t numFuncExport = 0;
  for (const Export& exp : module->exports()) {
    ...
    if (fuzzingSafe && exp.kind() == DefinitionKind::Function) {
      JSString* ftStr =
          FuncTypeToString(cx, funcExports[numFuncExport++].funcType());

   ...

But note the funcExports table only has one element because func 0 is exported twice. Hence the second reference into funcExports will be OOB.

This specific problem is shell-only and --fuzzing-safe only, but of course who knows what we'll find once we fix it.

This fixes some debugging code that assumed a function could be exported only
once. This code has been unchanged at least since the Big Source Reformatting
and is not a recent regression. This particular bug is clearly specific to
fuzzing-safe.

sec-other since fuzzing-safe only; will land on Nightly.

Keywords: sec-other
Attachment #9156646 - Attachment description: Bug 1645610 - Use the export's funcIndex to get the func export. r?bbouvier → Bug 1645610 - Use the export's funcIndex to get the func export. r=bbouvier
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 7 months ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla79

Ok, so no bounty I guess?

I also have a Spidermonkey denial-of-service bug (recursion stack overflow). Should I report this privately or publicly to Bugzilla?

(In reply to Guido Vranken from comment #9)

I also have a Spidermonkey denial-of-service bug (recursion stack overflow). Should I report this privately or publicly to Bugzilla?

Best to report privately, just to be safe. That said, in general stack overflow isn't security-sensitve due to guard pages on every modern OS.

Duplicate of this bug: 1647053
Crash Signature: [@ FuncTypeToString]

(In reply to Guido Vranken from comment #9)

Ok, so no bounty I guess?

Sorry, not this time.

Flags: sec-bounty? → sec-bounty-
Flags: qe-verify-
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [reporter-external] [client-bounty-form] [verif?][post-critsmash-triage]
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.