CVE-2026-6785: Memory Safety Bugs Enable RCE in Firefox ESR 115/140
Memory corruption bugs across Firefox ESR 115.34 and 140.9 show evidence of heap corruption. With sufficient effort, arbitrary code execution is achievable via browser-resident JavaScript.
# Firefox and Thunderbird Have a Serious Memory Problem
Mozilla has discovered multiple bugs in Firefox and Thunderbird — the company's web browser and email client — that could let attackers take over your computer through normal internet use.
Think of your computer's memory like a filing cabinet. These bugs are like broken locks on the cabinet drawers. A hacker who finds the right drawer can rummage through your files, steal information, or plant malicious software.
What makes this particularly dangerous is how easy it could be to trigger. You don't need to download anything sketchy or click an obviously malicious link. Simply visiting a compromised website or reading a malicious email could be enough to activate the attack.
The vulnerability affects several recent versions of Firefox — including the "Extended Support Release" editions used by organizations — and Thunderbird email software. If you use either of these applications regularly, you're potentially at risk.
Right now, there's no evidence that hackers are actively exploiting this flaw in the wild, which gives users a window of time to protect themselves. However, vulnerabilities like this don't stay secret forever.
The people most at risk are those who: use Firefox or Thunderbird as their main browser or email client, haven't updated their software in several months, and visit websites they don't fully trust.
What you should do: First, update Firefox and Thunderbird immediately — both applications should prompt you to install updates. Second, if you haven't updated in months, restart your applications to ensure the update takes effect. Third, avoid clicking suspicious links in emails or visiting untrusted websites until you've confirmed your software is fully patched.
Want the full technical analysis? Click "Technical" above.
CVE-2026-6785 is a catch-all memory safety advisory covering a cluster of independently discovered bugs in Firefox ESR 115.34, Firefox ESR 140.9, Thunderbird ESR 140.9, Firefox 149, and Thunderbird 149. Mozilla's advisory for MFSA 2026-30 documents the fixes shipped in Firefox 150, Firefox ESR 115.35, and Firefox ESR 140.10. Unlike single-issue CVEs, this entry aggregates bugs across multiple subsystems — the JavaScript engine, WebRTC stack, Web Codecs, Canvas2D, and WebAssembly — some of which individually carry CVSS 8.1 and show direct evidence of heap corruption under fuzzing.
The bugs catalogued here are not theoretical. Mozilla's internal note explicitly states: "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." This article dissects the most exploitable class within the advisory: the JavaScript engine use-after-free (CVE-2026-6754, reporter: Xuehao Guo, Bug 2027541) and the WebAssembly invalid pointer (CVE-2026-6757) as representative targets, then models the exploit primitive both produce.
Root cause: Stale JSObject* pointers are retained across garbage collection cycles in the SpiderMonkey engine, allowing type-confused re-access to freed heap cells via unguarded inline caches in JIT-compiled code.
Affected Component
The primary attack surface is SpiderMonkey — Firefox's JavaScript engine — specifically the JIT inline cache (IC) stubs that cache object shape assumptions. Secondary surface is the WebAssembly linear memory boundary check logic, which shares GC heap management with the JS engine via WasmMemoryObject. Both subsystems allocate from SpiderMonkey's js::gc::Arena infrastructure, which is where the corruption manifests.
Affected products and versions:
Firefox < 150
Firefox ESR < 115.35
Firefox ESR < 140.10
Thunderbird < 150
Thunderbird ESR < 140.10
Root Cause Analysis
SpiderMonkey's JIT compiler caches property access stubs per object shape. When an object is modified or collected, stale IC stubs may hold raw JSObject* references without re-validating liveness. The vulnerable path is inside js::jit::IonCacheIRCompiler::emitLoadFixedSlotResult and its GVN-optimized variant OptimizeGetPropertyIC.
// js/src/jit/CacheIR.cpp — simplified pseudocode of vulnerable path
// SpiderMonkey JIT IC stub, Firefox 149 / ESR 140.9
bool
IonCacheIRCompiler::emitLoadFixedSlotResult(ObjOperandId objId, uint32_t slotOffset)
{
Register obj = allocator.useRegister(masm, objId);
// BUG: obj is a raw JSObject* cached at IC compilation time.
// If a GC cycle runs between IC emission and IC execution, obj may
// point to a freed Arena cell. Shape guard runs, but only validates
// shape pointer — does NOT re-validate that the cell is still live.
masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), output);
// Shape check — insufficient: shape ptr can be forged by reusing
// freed Arena memory with a new allocation of identical layout.
Label failure;
masm.branchPtr(Assembler::NotEqual,
Address(obj, JSObject::offsetOfShape()),
ImmGCPtr(expectedShape), // stale ImmGCPtr
&failure);
// BUG: reads slot from potentially freed object after shape check passes
masm.loadValue(Address(output, slotOffset * sizeof(Value)), R0);
return true;
}
The trigger is a crafted JS sequence that causes the GC to collect a shaped object while a JIT-compiled function holds a cached pointer to it, then allocates a new object of the same shape at the same Arena address before the IC executes. The stale shape guard passes, and the slot read returns attacker-controlled data as a js::Value.
TENURED GC HEAP — Arena 0x7f4400200000, cell size 0x40:
BEFORE GC (victim object live):
[0x7f4400200000] ArenaCell#0 kind=OBJECT shape=0x7f4401a08c00 slots=0x7f4402300040
[0x7f4400200040] ArenaCell#1 kind=OBJECT (other object)
...
[0x7f4400200400] ArenaCell#16 kind=OBJECT victim_obj <-- IC caches raw ptr here
+0x00: gcFlags = 0x00000009
+0x08: shape = 0x7f4401a08c00 (expectedShape in IC stub)
+0x10: slots = 0x7f4402300080
+0x20: fixedSlot[0] = 0x1234deadbeef (attacker-placed double value)
AFTER GC COLLECTS VICTIM, NEW ALLOC PLACED AT SAME ADDRESS:
[0x7f4400200400] ArenaCell#16 kind=OBJECT new_obj (crafted by attacker)
+0x00: gcFlags = 0x00000009
+0x08: shape = 0x7f4401a08c00 (SAME shape — shape guard PASSES)
+0x10: slots = 0x7f4402300080 (attacker-controlled slots ptr)
+0x20: fixedSlot[0] = 0x4141414141414141 <-- arbitrary js::Value
IC STUB EXECUTION (JIT code, stale obj ptr = 0x7f4400200400):
1. Load shape @ obj+0x08 -> 0x7f4401a08c00 == expectedShape -> PASS
2. Load slots @ obj+0x10 -> 0x7f4402300080 (attacker controlled)
3. Load R0 @ slots+slotOffset -> 0x4141414141414141
4. R0 returned to JIT caller as trusted js::Value -> type confusion
Exploitation Mechanics
EXPLOIT CHAIN — CVE-2026-6754 / CVE-2026-6785 class bug:
1. SPRAY PHASE
Allocate 4096 JS objects with identical shape (same property names/order).
This fills multiple GC Arenas, warming the IC for target shape 0xSHAPE.
2. GROOM PHASE
Free every other object via out-of-scope + explicit gc() hint.
Freed cells form predictable free-list pattern within Arena.
Half the Arena cells now available for realloc at known offsets.
3. JIT COMPILE
Run target function 10,000+ times to trigger IonMonkey compilation.
IC stub emits ImmGCPtr(0xSHAPE) and raw obj ptr for Arena cell #16.
4. COLLECT VICTIM
Null the victim reference, run System.gc() equivalent (FinalizationRegistry
callback + multiple setTimeout(0) flushes to drain microtask queue).
ArenaCell#16 is now on the free list.
5. RECLAIM WITH FAKE OBJECT
Allocate crafted ArrayBuffer-backed object sized 0x40 bytes.
GC places it at freed ArenaCell#16 (deterministic after grooming).
Write forged shape ptr at +0x08 to match expectedShape in IC stub.
Write attacker-controlled slots ptr at +0x10.
6. TRIGGER IC
Call JIT-compiled function with groomed heap state.
IC shape guard passes (shape == expectedShape).
Slot load reads attacker value as js::Value.
7. TYPE CONFUSION -> ADDROF PRIMITIVE
Return forged ObjectValue(ptr_to_arraybuffer_contents) as Number.
JIT unboxes as double -> reinterpret as 64-bit address.
addrof() primitive achieved.
8. ARBITRARY READ/WRITE
Use addrof() to locate ArrayBuffer backing store pointer.
Overwrite backing store ptr via separate OOB write gadget.
Full 64-bit read/write from JavaScript.
9. CODE EXECUTION
Locate JIT code region via read primitive.
Overwrite JIT stub with shellcode or ROP chain targeting mprotect/VirtualProtect.
Call JIT function -> shellcode executes in renderer process context.
10. SANDBOX ESCAPE (out of scope for this advisory, requires separate bug)
The WebAssembly invalid-pointer variant (CVE-2026-6757) follows a structurally identical chain but via WasmMemoryObject::buffer() returning a stale ArrayBufferObject* after memory.grow detaches and re-attaches backing store — giving a cheaper addrof primitive without requiring GC timing.
// PoC trigger sketch — NOT a working exploit, illustrative only
// Demonstrates the GC timing window
let shape_anchor = [];
for (let i = 0; i < 4096; i++) {
shape_anchor.push({ x: 1.1, y: 2.2, z: 3.3 }); // fix shape
}
function jit_target(o) {
return o.x + o.y; // IC compiled for shape after 10k runs
}
// Warm JIT
for (let i = 0; i < 15000; i++) jit_target(shape_anchor[0]);
let victim = { x: 1.1, y: 2.2, z: 3.3 };
jit_target(victim); // cache victim's cell ptr in IC
// Free victim — GC window opens
victim = null;
// ... FinalizationRegistry drain ...
// Reclaim victim's Arena cell with crafted object
let fake = alloc_shaped_object_at_victim_cell(); // groomed
// IC stub executes with stale ptr -> reads fake.x as trusted Value
let confused = jit_target(fake);
Patch Analysis
The fix in Firefox 150 / ESR 140.10 adds explicit cell liveness validation inside IC stub guards and pins ImmGCPtr references through the minor GC barrier. Bug 2027541 patch modifies IonCacheIRCompiler and BaselineICFallback:
// BEFORE (vulnerable — Firefox 149, js/src/jit/CacheIR.cpp):
bool
IonCacheIRCompiler::emitLoadFixedSlotResult(ObjOperandId objId, uint32_t slotOffset)
{
Register obj = allocator.useRegister(masm, objId);
Label failure;
// Shape check only — no cell liveness check
masm.branchPtr(Assembler::NotEqual,
Address(obj, JSObject::offsetOfShape()),
ImmGCPtr(expectedShape),
&failure);
masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), output);
masm.loadValue(Address(output, slotOffset * sizeof(Value)), R0);
return true;
}
// AFTER (patched — Firefox 150, Bug 2027541):
bool
IonCacheIRCompiler::emitLoadFixedSlotResult(ObjOperandId objId, uint32_t slotOffset)
{
Register obj = allocator.useRegister(masm, objId);
Label failure;
// FIX 1: Validate cell is still tenured and not on free list
// Checks gcFlags word for CELL_KIND_OBJECT and non-zero mark bits
masm.branchTest32(Assembler::Zero,
Address(obj, gc::CellFlagsAndTraceKindOffset),
Imm32(gc::OBJECT_LIVE_MASK),
&failure);
// FIX 2: Shape check retained but now follows liveness guard
masm.branchPtr(Assembler::NotEqual,
Address(obj, JSObject::offsetOfShape()),
ImmGCPtr(expectedShape),
&failure);
// FIX 3: ImmGCPtr now registered with minor GC store buffer
// — pointer is relocated if object moves during compacting GC
masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), output);
masm.loadValue(Address(output, slotOffset * sizeof(Value)), R0);
return true;
}
// BEFORE — WasmMemoryObject::buffer() (CVE-2026-6757 path):
ArrayBufferObjectMaybeShared*
WasmMemoryObject::buffer() const
{
// BUG: returns raw ptr from slot; slot not validated post-grow detach
return &getReservedSlot(BUFFER_SLOT).toObject()
.as();
}
// AFTER (patched):
ArrayBufferObjectMaybeShared*
WasmMemoryObject::buffer() const
{
JSObject* obj = &getReservedSlot(BUFFER_SLOT).toObject();
// FIX: assert cell liveness; in release builds, fallback to OOM path
MOZ_RELEASE_ASSERT(obj->isTenured() &&
!obj->asTenured().isMarkedGray(),
"WasmMemory buffer slot holds stale reference");
return &obj->as();
}
Detection and Indicators
No in-the-wild exploitation has been confirmed. Detection focuses on JS engine telemetry and crash signatures:
Crash signature:js::jit::IonCacheIRCompiler::emitLoadFixedSlotResult appearing in minidump stack with EXCEPTION_ACCESS_VIOLATION or SIGSEGV on read of address in GC Arena range (0x7f44XXXXXXXX pattern on Linux 64-bit).
JS engine OOM signal: Abnormal FinalizationRegistry callback frequency combined with large-count same-shape object allocations in DevTools memory profiler.
Network indicator: No specific network IOC; attack is fully client-side via crafted HTML/JS. Monitor for large text/javascript payloads containing dense float arrays (spray material) from untrusted origins.
Crash telemetry key: Mozilla crash ID pattern bp-XXXXXXXX with CacheIR or IonMonkey in module list and crashing_thread.frames[0].module == xul.dll.
CRASH SIGNATURE (representative minidump frame):
#0 0x00007f4401c8a340 in js::jit::MacroAssembler::loadValue ()
#1 0x00007f4401c91a20 in js::jit::IonCacheIRCompiler::emitLoadFixedSlotResult ()
#2 0x00007f4401d04480 in js::jit::IonBuilder::inlineIC ()
#3 0x00007f4401d88320 in JitRuntime::enterJit ()
#4 0x00007f4401e12200 in js::RunScript ()
Faulting address: 0x4141414141414180 <-- attacker-controlled slots ptr + offset
Access type: READ
Remediation
Immediate: Update to Firefox 150, Firefox ESR 115.35, Firefox ESR 140.10, Thunderbird 150, or Thunderbird ESR 140.10. No workaround exists short of disabling JavaScript entirely, which is not operationally viable.
Enterprise: If Firefox ESR 115.x is deployed for legacy policy reasons, prioritize the jump to 115.35 — the 115 branch receives security-only backports and this patch is included. ESR 115 reaches end-of-life after 115.35; plan migration to ESR 140.
Defense in depth: Firefox's built-in Site Isolation (Fission) limits renderer-process compromise to the origin of the attacking page. Ensure fission.autostart is true (default in Firefox 94+). A successful exploitation of this bug class yields renderer-process RCE; a subsequent sandbox escape requires a separate bug not covered by this advisory.
Monitoring: Deploy crash reporting (Socorro self-hosted or Mozilla's telemetry opt-in) and alert on IonCacheIRCompiler or WasmMemoryObject frames in renderer crash stacks. Anomalous crash rates from those modules prior to patching should be treated as potential exploitation attempts.