Skip to content

Link wasm-wasm directly in the fuzzer#8794

Open
kripken wants to merge 1 commit into
WebAssembly:mainfrom
kripken:direct2
Open

Link wasm-wasm directly in the fuzzer#8794
kripken wants to merge 1 commit into
WebAssembly:mainfrom
kripken:direct2

Conversation

@kripken
Copy link
Copy Markdown
Member

@kripken kripken commented Jun 1, 2026

This avoids passing JSPI-wrapped exports from one wasm module as the imports
to another. Without this, the stack traces and erroring behavior become very
confusing. I noticed this during #8736 (fuzzing start) but I don't think it's limited to
the start function.

With that said, I admit I don't totally understand why the stack traces etc. get
very odd before this PR... async stuff is complex. But this seems like the right
thing anyhow?

@kripken kripken requested a review from brendandahl June 1, 2026 21:47
@kripken kripken requested a review from a team as a code owner June 1, 2026 21:47
@kripken kripken requested review from tlively and removed request for a team June 1, 2026 21:47
@kripken
Copy link
Copy Markdown
Member Author

kripken commented Jun 1, 2026

Emscripten also has some complexity around the original exports and the instrumented ones, which I also am not totally sure I follow,

https://github.com/emscripten-core/emscripten/blob/1465d28c96510a9b2a69854bdc5e062f5f9e0790/src/lib/libasync.js#L57

@brendandahl
Copy link
Copy Markdown
Collaborator

I can't seem to find the conversation, but IIRC one of the other v8 folks said that the wrapped version should behave the same as the original. Maybe a bug in v8? I can poke around a bit more on it tomorrow.

@kripken
Copy link
Copy Markdown
Member Author

kripken commented Jun 1, 2026

Thanks, then let me reduce my testcase here, and I'll get that to you.

@kripken
Copy link
Copy Markdown
Member Author

kripken commented Jun 1, 2026

Ok, here is the testcase:

https://gist.github.com/kripken/1759e0f618e0a755e5a69f28f6b78142

$ diff -U2 a.js b.js
--- a.js	2026-06-01 16:41:32.078358653 -0700
+++ b.js	2026-06-01 16:41:33.662363189 -0700
@@ -203,5 +203,4 @@
 // whose keys are strings and whose values are the corresponding exports).
 var exports = {};
-var rawExports = {};
 
 // Also track exports in a list, to allow access by index. Each entry here will
@@ -520,5 +519,5 @@
     assert(secondBinary);
     // Provide the primary module's exports to the secondary.
-    imports['primary'] = rawExports;
+    imports['primary'] = exports;
   }
 
@@ -554,5 +553,4 @@
     var key = e.name;
     var value = instance.exports[key];
-    rawExports[key] = value;
     value = wrapExportForJSPI(value);
     exports[key] = value;

The only diff between the JS files is to use the raw exports as the imports of the other module. And the behavior is very different:

$ v8 --experimental-wasm-custom-descriptors --fuzzing --experimental-wasm-acquire-release a.js -- a.0.wasm a.1.wasm
V8 is running with experimental features enabled. Stability and security will suffer.
exception thrown: failed to instantiate module: RuntimeError: unreachable
[fuzz-exec] export global$_1
[LoggingExternalInterface logging function]
[fuzz-exec] export 0_invoker
[fuzz-exec] export 2_invoker
[fuzz-exec] export 3_invoker
[fuzz-exec] export 4_invoker
[fuzz-exec] export 6_invoker
[fuzz-exec] export 7_invoker
exception thrown: RuntimeError: unreachable
[fuzz-exec] export 8_invoker
[fuzz-exec] export 10_invoker
[fuzz-exec] export 11_invoker
[fuzz-exec] export func
exception thrown: RuntimeError: unreachable
[fuzz-exec] export func_invoker
[fuzz-exec] export func_33_invoker
[fuzz-exec] export new
exception thrown: RuntimeError: unreachable
[fuzz-exec] export get
exception thrown: RuntimeError: unreachable
[fuzz-exec] export set_get
exception thrown: RuntimeError: unreachable
[fuzz-exec] export global$_2
[LoggingExternalInterface logging function]
$ echo $?
0

versus

$ v8 --experimental-wasm-custom-descriptors --fuzzing --experimental-wasm-acquire-release b.js -- a.0.wasm a.1.wasm
V8 is running with experimental features enabled. Stability and security will suffer.
[fuzz-exec] export global$_1
[LoggingExternalInterface logging function]
[fuzz-exec] export 0_invoker
[fuzz-exec] export 2_invoker
[fuzz-exec] export 3_invoker
[fuzz-exec] export 4_invoker
[fuzz-exec] export 6_invoker
[fuzz-exec] export 7_invoker
exception thrown: RuntimeError: unreachable
[fuzz-exec] export 8_invoker
[fuzz-exec] export 10_invoker
[fuzz-exec] export 11_invoker
[fuzz-exec] export func
exception thrown: RuntimeError: unreachable
[fuzz-exec] export func_invoker
[fuzz-exec] export func_33_invoker
[fuzz-exec] export new
exception thrown: RuntimeError: unreachable
[fuzz-exec] export get
exception thrown: RuntimeError: unreachable
[fuzz-exec] export set_get
exception thrown: RuntimeError: unreachable
[fuzz-exec] export global$_2
[LoggingExternalInterface logging function]
wasm-function[6]:0x134: RuntimeError: unreachable
RuntimeError: unreachable
    at wasm://wasm/b12b3406:wasm-function[6]:0x134
    at build (b.js:526:16)
    at b.js:719:3

1 pending unhandled Promise rejection(s) detected.
$ echo $?
1

The start function in the second module is important somehow - I only see this when fuzzing start functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants