High CVSS 7.3 firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
7.3
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 130. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentSpiderMonkey
Bug ClassMemory Corruption
Tracker1917807
Fix commit16781a13b5ec (firefox) +5/-2
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
CISA KEVNot listed
CreditedThe Mozilla Fuzzing Team
Disclosed2024-10-01

Background

JIT entry stub
The machine-code trampoline generated by GenerateJitEntry that lets JIT-compiled JavaScript call directly into a WebAssembly exported function.
`ExitFooterFrame`
A fixed-layout frame that SpiderMonkey pushes when transitioning out of generated code, whose ExitFooterFrame::Size() reserves space for bookkeeping such as the exit reason and saved frame pointer.
`WasmStackAlignment`
The required byte alignment for the wasm ABI stack, which arguments passed on the stack must respect for correct and safe access.
Stack argument area
The region of the stack reserved by StackArgBytesForWasmABI to hold call arguments that do not fit in registers.

Root Cause Analysis

In GenerateJitEntry, the stub computed the amount of stack space to reserve by adding the raw ExitFooterFrame::Size() to the byte count returned by StackArgBytesForWasmABI(funcType), producing normalBytesNeeded (and similarly oolBytesNeeded for the out-of-line coercion path). Because ExitFooterFrame::Size() is not necessarily a multiple of WasmStackAlignment, the code that subsequently placed the aligned stack-argument area could round the argument base up for alignment, causing that area to overlap the memory belonging to the ExitFooterFrame. Writes to stack arguments then clobbered the footer frame (or vice versa), corrupting the frame’s saved state and violating the invariant that the exit footer and the argument region occupy disjoint stack ranges.

The fix precomputes AlignedExitFooterFrameSize as AlignBytes(ExitFooterFrame::Size(), WasmStackAlignment) and uses that aligned value in both normalBytesNeeded and oolBytesNeeded, guaranteeing the argument area starts on an aligned boundary that lies strictly above the footer frame so the two regions can no longer overlap.

Key insight
The core mistake was reserving space with an unaligned ExitFooterFrame::Size() while the argument area was placed at an aligned offset, so alignment padding silently overlapped the footer frame; the fix rounds the footer size up to WasmStackAlignment before adding argument bytes, keeping the regions disjoint.

Attack Path

  1. Reach the JIT entry stub Attacker-controlled JavaScript repeatedly calls a WebAssembly exported function so the engine generates and executes the GenerateJitEntry trampoline.
  2. Choose a colliding signature By defining a wasm function whose funcType yields a StackArgBytesForWasmABI size that forces alignment padding, the attacker makes the aligned stack-argument base overlap the ExitFooterFrame.
  3. Trigger the overlap During the transition, argument marshalling writes into the overlapping region, overwriting fields of the ExitFooterFrame such as saved frame-pointer or exit-reason data.
  4. Corrupt control state The clobbered footer frame is later consumed during the exit, producing a corrupted stack view that can redirect execution or misinterpret memory.
  5. Escalate to code execution With controlled overwrite of frame metadata, the attacker steers the corruption toward a controllable pointer to achieve arbitrary code execution in the content process.

Impact Assessment

An attacker who can run JavaScript/WebAssembly in a content process gains a memory-corruption primitive by overwriting ExitFooterFrame state on the stack, which with sufficient effort can be leveraged into arbitrary code execution. The corruption occurs in the sandboxed content process during a JIT-to-wasm call, and the only precondition is the ability to define and invoke a wasm export with a signature whose stack-argument sizing triggers the misaligned overlap.

Files Changed

  • js/src/wasm/WasmStubs.cpp

Audit Directions

  • Unaligned size arithmetic
    Search for stack- or frame-size computations that add a raw ::Size() to an aligned region without wrapping it in AlignBytes, since downstream alignment can create overlap.
  • Stub frame layout
    Review other trampoline generators in js/src/wasm/WasmStubs.cpp and the Jit exit-frame code that mix ExitFooterFrame reservations with StackArgBytesForWasmABI results for the same disjointness invariant.
  • ABI alignment assumptions
    Audit places relying on WasmStackAlignment (or analogous alignment constants) where the base offset is aligned but the reserved size is not, to catch silent padding-induced aliasing.
diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp
index 748717f01e4..70c6c98d919 100644
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -944,15 +944,18 @@ static bool GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex,
 
   MOZ_ASSERT(masm.framePushed() == 0);
 
+  // Avoid overlapping aligned stack arguments area with ExitFooterFrame.
+  const unsigned AlignedExitFooterFrameSize =
+      AlignBytes(ExitFooterFrame::Size(), WasmStackAlignment);
   unsigned normalBytesNeeded =
-      ExitFooterFrame::Size() + StackArgBytesForWasmABI(funcType);
+      AlignedExitFooterFrameSize + StackArgBytesForWasmABI(funcType);
 
   MIRTypeVector coerceArgTypes;
   MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Int32));
   MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer));
   MOZ_ALWAYS_TRUE(coerceArgTypes.append(MIRType::Pointer));
   unsigned oolBytesNeeded =
-      ExitFooterFrame::Size() + StackArgBytesForWasmABI(coerceArgTypes);
+      AlignedExitFooterFrameSize + StackArgBytesForWasmABI(coerceArgTypes);
 
   unsigned bytesNeeded = std::max(normalBytesNeeded, oolBytesNeeded);
 
Loading diff…