CVE-2026-43745
Overview
Background
- Legacy vs modern wasm exceptions
- The original exception-handling proposal (try/catch/rethrow) and the newer try_table construct; a module must not mix them.
- Streaming vs non-streaming compilation
- Wasm can be compiled from a full buffer (WebAssembly.Module) or incrementally as bytes arrive (instantiateStreaming); both must reach the same accept/reject decision.
- EntryPlan / IPIntPlan
- JSC compilation plans that drive wasm function compilation; IPIntPlan handles the in-place interpreter/streaming completion.
- Validation parity
- The security-relevant property that all compilation entry points enforce identical validation, so no path accepts a module another rejects.
Root Cause Analysis
WebAssembly modules may not mix the legacy exception-handling proposal (try/catch, rethrow) with the modern try_table proposal in the same module; JSC rejects such modules with a CompileError. That guard was implemented only in the non-streaming compilation path: EntryPlan::compileFunctions() checked m_moduleInformation->m_usesModernExceptions && m_usesLegacyExceptions and called fail(...). The streaming compilation path (IPIntPlan::completeInStreaming()) had no equivalent check, so streaming and non-streaming compilation disagreed on the same bytes: a module mixing both EH schemes was rejected when compiled normally but accepted when compiled via the streaming API. Accepting such a module runs it through exception-handling codegen/metadata that assumes a single EH scheme, violating an invariant the rest of the EH machinery relies on.
The fix factors the check into EntryPlan::failIfMixedExceptionHandlingProposals() (guarded by m_lock) and calls it from both the non-streaming path and IPIntPlan::completeInStreaming(), restoring the invariant that streaming and non-streaming compilation agree — both fail with a CompileError — for a module that mixes legacy exceptions and try_table. The added JSTests pin that streaming/non-streaming outcomes match for try_table-only and mixed-EH modules. That downstream EH handling of a wrongly-accepted mixed module is unsafe is an inference; the patch establishes the validation-parity gap and closes it.
Attack Path
- Craft a mixed-EH module Build a wasm module that uses both a legacy try/catch (or rethrow) and a modern try_table in its functions.
- Compile via the streaming API Instantiate it through streaming compilation (WebAssembly.instantiateStreaming / the streaming compiler), whose completion path lacked the mixed-EH guard.
- Bypass validation Streaming accepts the module that non-streaming would reject with a CompileError, so it is compiled and instantiable.
- Exercise inconsistent EH codegen Running the module drives exception-handling code paths that assume a single EH scheme (inference), risking mis-compiled EH and a crash/memory unsafety in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
EntryPlan::failIfMixedExceptionHandlingProposalsSource/JavaScriptCore/wasm/WasmEntryPlan.cpp |
added | Extracts the 'uses both legacy exceptions and try_table' check into one lock-guarded helper that fails the plan. |
EntryPlan::compileFunctionsSource/JavaScriptCore/wasm/WasmEntryPlan.cpp |
modified | Now calls failIfMixedExceptionHandlingProposals() instead of an inline check. |
IPIntPlan::completeInStreamingSource/JavaScriptCore/wasm/WasmIPIntPlan.cpp |
modified | Adds the previously-missing failIfMixedExceptionHandlingProposals() call so streaming rejects mixed-EH modules too. |
EntryPlan::failIfMixedExceptionHandlingProposals (decl)Source/JavaScriptCore/wasm/WasmEntryPlan.h |
modified | Declares the helper WTF_REQUIRES_LOCK(m_lock). |
Files Changed
JSTests/wasm/stress/streaming-compile-try-table-agreement.jsSource/JavaScriptCore/wasm/WasmEntryPlan.cppSource/JavaScriptCore/wasm/WasmEntryPlan.hSource/JavaScriptCore/wasm/WasmIPIntPlan.cpp
Audit Directions
- Other single-path validationDiff EntryPlan/BBQ/OMG/IPInt streaming vs non-streaming completion for other checks present in one path only; grep for fail(makeString( guards not mirrored in completeInStreaming.
- Proposal-mixing guardsAudit uses of m_usesModernExceptions/m_usesLegacyExceptions and other ‘usesX’ module flags to ensure every consumer enforces the same constraints.
- Streaming completion pathsReview all *Plan::completeInStreaming()/completeSyncIfPossible() to confirm they run the full set of module-level validations before complete().