← All issues

[17] [JSC] Save source offset for evaluating Wasm constant expressions

Severity: Medium | Component: JSC WebAssembly module loader | b05d16f

Rated Medium because the observable effect is one uninitialised 64-bit stack word leaked per failed extended-const-expr evaluation via WebAssembly.RuntimeError.message, sufficient as an info-leak primitive (e.g., to defeat ASLR) but not a read-of-attacker-chosen-address primitive.

Source/JavaScriptCore/wasm/WasmConstExprGenerator.cpp

- ConstExprGenerator(Mode mode, const ModuleInformation& info, JSWebAssemblyInstance* instance)
+ ConstExprGenerator(Mode mode, size_t offsetInSource, const ModuleInformation& info, JSWebAssemblyInstance* instance)
: m_mode(mode)
+ , m_offsetInSource(offsetInSource)
, m_info(info)
, m_instance(instance)
...
Mode m_mode;
- size_t m_offsetInSource;
+ size_t m_offsetInSource { 0 };

The Evaluate-mode constructor of ConstExprGenerator gains an offsetInSource parameter, threaded through evaluateExtendedConstExpr and WebAssemblyModuleRecord::evaluateConstantExpression. ModuleInformation::constantExpressions becomes Vector<std::pair<Vector<uint8_t>, size_t>> (typedef ConstantExpressionAndSourceOffset); SectionParser::parseInitExpr records initialOffset + m_offsetInSource alongside the expression bytes. As defence-in-depth, m_offsetInSource gains a { 0 } default initializer, replacing the previous uninitialised declaration.

Uninitialized member of a stack-allocated object reaches a web-visible error string via integer-to-text formatting.

Wasm extended constant expressions (the GC / ext-const proposal) allow operations beyond simple constants in initializer positions — array.new, struct.new, i32.add. They are parsed at module compile time and re-executed at instance creation, because their result depends on imports/globals. JSC routes both phases through ConstExprGenerator with two modes: Validate (during SectionParser::parseInitExpr) and Evaluate (during WebAssemblyModuleRecord::evaluateConstantExpression). Each phase shares a fail() helper that formats WebAssembly.Module doesn't parse at byte N: ... using m_parser->offset() + m_offsetInSource. Failures inside fail() become the .message of a JSWebAssemblyRuntimeError thrown to JS.

ConstExprGenerator::m_offsetInSource was declared without an initializer. Only the Validate-mode constructor took an offsetInSource parameter; the Evaluate-mode constructor did not assign it. ConstExprGenerator is stack-allocated inside evaluateExtendedConstExpr, so the field held arbitrary stack data from prior frames.

🔒

An information-disclosure path through the WebAssembly module loader, including the chain that converts a parser diagnostic into a leaked memory primitive.

Subscribe to read more

🔒

Several reusable audit patterns identified across JSC parsers and error formatters, with concrete starting greps for variant discovery.

Subscribe to read more