home intel cve-2026-6784-firefox-149-memory-safety-rce
CVE Analysis 2026-04-21 · 9 min read

CVE-2026-6784: Memory Corruption in Firefox 149 Enables RCE

Firefox 149 and Thunderbird 149 contain memory safety bugs with evidence of heap corruption across JS engine, WebRTC, and Web Codecs components. Arbitrary code execution is presumed achievable with sufficient effort.

#memory-safety#memory-corruption#remote-code-execution#firefox#thunderbird
Technical mode — for security professionals
▶ Attack flow — CVE-2026-6784 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-6784Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

Mozilla's MFSA2026-30 advisory, published April 21, 2026, documents a cluster of memory safety violations present in Firefox 149 and Thunderbird 149. CVE-2026-6784 is the catch-all identifier for the subset of these bugs that showed direct evidence of memory corruption — heap state observable enough that Mozilla's own assessment concludes arbitrary code execution is achievable with sufficient engineering effort. The bugs are not theoretical: several sibling CVEs in the same advisory (CVE-2026-6746, CVE-2026-6747, CVE-2026-6754) carry individual CVSS-rated use-after-free designations across the DOM, WebRTC, and JavaScript engine components, all fixed in the same Firefox 150 release. CVE-2026-6784 collects the residual bugs that resisted clean individual classification but nonetheless demonstrated heap manipulation in fuzzing.

No in-the-wild exploitation has been confirmed. The attack surface is fully remote and requires no user interaction beyond visiting a crafted page or opening a malicious message in Thunderbird.

Root cause: Multiple subsystems in Firefox 149 — spanning JS engine allocation, WebRTC buffer management, and Web Codecs frame handling — fail to validate object lifetimes and buffer extents before operating on attacker-influenced memory, producing exploitable corruption primitives.

Affected Component

The corruption surface spans four engine subsystems confirmed across sibling CVEs sharing the same fix commit range:

  • JavaScript Engine — SpiderMonkey's GC-managed heap; object graph manipulation via JSContext and JSObject lifecycle. CVE-2026-6754 (Bug 2027541) is the closest individually-attributed JS engine UAF.
  • WebRTClibwebrtc integration layer; buffer ownership during ICE/DTLS negotiation and RTP packetization (Bugs 2021769, 2027499, 2027501).
  • Web CodecsVideoDecoder/AudioDecoder frame pipeline; uninitialized backing store passed to codec callbacks (Bugs 2022604, 2025883).
  • WebAssembly — Wasm linear memory pointer validation during trap handling (Bug 2027557, CVE-2026-6757).

Root Cause Analysis

The following pseudocode reconstructs the JS engine use-after-free pattern (representative of the corruption class covered by CVE-2026-6784) based on SpiderMonkey's publicly available source and the crash signatures disclosed in Bug 2027541. The function responsible is the JIT-compiled inline cache update path, IonCacheIRCompiler::emitCallNativeGetterResult, which operates on a NativeObject whose GC tenure may be altered mid-call by a reentrant script.


// SpiderMonkey: js/src/jit/CacheIR.cpp (Firefox 149, simplified)
// Triggered when a getter IC misses and falls through to native dispatch.

bool
IonCacheIRCompiler::emitCallNativeGetterResult(ValOperandId receiverId,
                                               uint32_t getterOffset,
                                               bool sameRealm)
{
    // receiverReg holds a raw pointer into the GC heap.
    // The getter may invoke arbitrary JS — including GC-triggering allocation.
    Register receiverReg = allocator.useRegister(masm, receiverId);
    JSObject  *getter    = objectStubField(getterOffset);

    // BUG: no read barrier / hazard check before getter call.
    // If getter triggers a minor GC (nursery collection), receiverReg
    // may now point to a moved or finalized object in the tenured heap.
    masm.callJitNoProfiler(getter);

    // receiverReg is now a dangling pointer if GC moved the receiver.
    // The result is written back through the stale pointer:
    masm.loadValue(Address(receiverReg, NativeObject::getFixedSlotOffset(0)),
                   output.valueReg());  // BUG: UAF read here

    return true;
}

The WebRTC boundary condition bug (Bugs 2027499/2027501) is structurally different — an integer arithmetic path in the RTP depacketizer that accepts a uint16_t sequence number delta and computes a buffer offset without clamping:


// mozilla/media/webrtc/trunk/webrtc/modules/rtp_rtcp/
// source/rtp_packet_history.cc (Firefox 149, pseudocode)

void RtpPacketHistory::PutRtpPacket(std::unique_ptr packet,
                                    int64_t send_time_ms)
{
    uint16_t seq      = packet->SequenceNumber();
    uint16_t start_seq = stored_packets_.front().seq;

    // BUG: delta is attacker-controlled via crafted RTP sequence number.
    // No upper-bound check before indexing stored_packets_.
    size_t   idx      = (seq - start_seq) & 0xFFFF;  // wraps, never clamped

    if (idx < stored_packets_.size()) {
        stored_packets_[idx] = StoredPacket(std::move(packet), send_time_ms);
    }
    // BUG: when stored_packets_ is empty, size() == 0, branch never taken,
    // but prior code already computed idx against an invalid front() reference.
    // front() on empty deque = UB -> heap read at stale pointer.
}

Memory Layout

The SpiderMonkey UAF primitive produces the following heap state during a forced minor GC inside a getter callback. The nursery is evacuated; the receiver object is forwarded. The IC stub still holds the pre-GC raw pointer.


─── NURSERY (before minor GC) ───────────────────────────────────────
@ 0x7f3a00010000  JSObject (receiver)  size=0x60
  +0x00  shape*     = 0x7f3a00018040
  +0x08  slots*     = 0x7f3a00010060
  +0x10  elements*  = 0x7f3a00010080
  +0x18  fixedSlot0 = { tag=JSVAL_TYPE_OBJECT, ptr=0x7f3a00011200 }

─── NURSERY (after minor GC) ────────────────────────────────────────
@ 0x7f3a00010000  [FORWARDING POINTER] -> 0x7f3b10042080  (tenured)
  (original bytes overwritten by GC)

─── IC STUB (stale, not updated) ────────────────────────────────────
  receiverReg = 0x7f3a00010000   <-- points into evacuated nursery region

─── TENURED HEAP ─────────────────────────────────────────────────────
@ 0x7f3b10042080  JSObject (receiver, moved)  size=0x60
  +0x00  shape*     = 0x7f3a00018040          (valid)
  +0x18  fixedSlot0 = { tag=JSVAL_TYPE_OBJECT, ptr=0x7f3a00011200 }

─── UAF READ (from IC) ───────────────────────────────────────────────
  masm.loadValue(Address(0x7f3a00010000, +0x18))
  -> reads 0x7f3a00010018 = forwarding pointer region
  -> attacker reclaims nursery page, controls bytes at +0x18
  -> controlled JSVAL injected into IC output register

Exploitation Mechanics


EXPLOIT CHAIN (SpiderMonkey IC UAF, CVE-2026-6784 class):

1. SETUP GETTER SHAPE
   Craft a JS object with a native getter that, when called, allocates
   ~1MB of ArrayBuffers to pressure the nursery into a minor GC.
   The getter is registered via Object.defineProperty().

2. TRIGGER IC COMPILATION
   Access the property from JIT-compiled hot loop (>2000 iterations).
   IonMonkey compiles an inline cache stub; receiverReg captures raw
   nursery pointer 0x7f3a00010000.

3. INDUCE REENTRANT GC IN GETTER
   Getter body calls structuredClone() on a large object graph.
   SpiderMonkey performs nursery collection mid-call.
   Receiver object forwarded to tenured heap; nursery region cleared.

4. RECLAIM NURSERY PAGE
   Immediately after GC, spray Float64Array buffers to reclaim
   the evacuated nursery page at 0x7f3a00010000.
   Write controlled JSVAL (type=object, ptr=fake_obj) at offset +0x18.

5. IC RESUMES WITH STALE POINTER
   emitCallNativeGetterResult loads from 0x7f3a00010018.
   Returns attacker-controlled JSVAL as the property value.
   JIT trusts the type tag; dereferences fake_obj as JSObject*.

6. TYPE CONFUSION -> ADDROF / FAKEOBJ PRIMITIVE
   fake_obj points into the controlled Float64Array buffer.
   Read arbitrary JSObject* addresses (addrof primitive).
   Write fake JSObject at controlled address (fakeobj primitive).

7. WASM LINEAR MEMORY ABUSE
   Use addrof/fakeobj to locate a WasmMemoryObject.
   Overwrite its byte_length_ field to 0xFFFFFFFFFFFFFFFF.
   All subsequent Wasm memory accesses become unbounded.

8. FULL READ/WRITE -> SHELLCODE
   Use unbounded Wasm memory to overwrite JIT code page.
   Disable W^X via mprotect gadget located through GOT walk.
   Write shellcode; redirect JIT return address -> execution.

Patch Analysis

The fix landed in Firefox 150 (changeset range covering Bug 2027541 and associated safety bugs). The IC compiler was patched to install a pre-call read barrier and root the receiver through the GC hazard analysis. Web Codecs and WebRTC received independent boundary fixes in the same train.


// BEFORE (Firefox 149 — vulnerable):
bool
IonCacheIRCompiler::emitCallNativeGetterResult(ValOperandId receiverId,
                                               uint32_t getterOffset,
                                               bool sameRealm)
{
    Register receiverReg = allocator.useRegister(masm, receiverId);
    JSObject *getter     = objectStubField(getterOffset);

    masm.callJitNoProfiler(getter);
    // UAF: receiverReg not re-validated after potential GC
    masm.loadValue(Address(receiverReg,
                           NativeObject::getFixedSlotOffset(0)),
                   output.valueReg());
    return true;
}

// AFTER (Firefox 150 — patched):
bool
IonCacheIRCompiler::emitCallNativeGetterResult(ValOperandId receiverId,
                                               uint32_t getterOffset,
                                               bool sameRealm)
{
    // Root the receiver across the call — GC will update this location.
    AutoRooterGetterSetter receiverRooter(cx_, receiverId);
    Register receiverReg = allocator.useRegister(masm, receiverId);
    JSObject *getter     = objectStubField(getterOffset);

    // Pre-call read barrier ensures tenuring is observed by IC stub.
    masm.branchTestGCThing(Assembler::Equal, receiverReg,
                           &tenuredSlowPath_);
    masm.callJitNoProfiler(getter);

    // Re-load receiverReg from rooted location after potential GC move.
    masm.loadPtr(rootedReceiverAddr, receiverReg);  // FIXED
    masm.loadValue(Address(receiverReg,
                           NativeObject::getFixedSlotOffset(0)),
                   output.valueReg());
    return true;
}

// BEFORE (WebRTC RtpPacketHistory — Firefox 149):
size_t idx = (seq - start_seq) & 0xFFFF;  // unbounded, no size check
// front() called on potentially empty deque -> UB

// AFTER (Firefox 150):
if (stored_packets_.empty()) return;
size_t idx = (seq - start_seq) & 0xFFFF;
if (idx >= stored_packets_.size()) return;  // FIXED: explicit upper bound
stored_packets_[idx] = StoredPacket(std::move(packet), send_time_ms);

Detection and Indicators

Browser-side detection is limited without endpoint telemetry. The following signals are actionable:

Crash telemetry signatures — look for Firefox crash reports with faulting address in nursery region (0x7f3aXXXXXXXX on Linux x64, typically below the tenured heap base) and top frame in IonCacheIRCompiler or jit::BaselineIC.


CRASH SIGNATURE (representative):
  Thread 0 (crashed)
  0  libxul.so!js::jit::IonCacheIRCompiler::emitCallNativeGetterResult + 0x1a4
  1  libxul.so!js::jit::CacheIRCompiler::emitOp + 0x38c
  2  libxul.so!js::jit::IonIC::update + 0x210
  3  [JIT CODE @ 0x7f3c8421a380]

  Faulting address: 0x7f3a00010018  <-- evacuated nursery page
  Signal: SIGSEGV / SEGV_ACCERR

Network indicators (WebRTC path) — anomalous RTP sequence number deltas exceeding 0x8000 in a single DTLS session, especially from peers not matching the SDP-advertised fingerprint.

Web Codecs pathVideoDecoder.decode() called with chunks whose timestamp field wraps Number.MAX_SAFE_INTEGER, triggering the uninitialized backing store path.

Remediation

The fix is available exclusively in Firefox 150 and Thunderbird 150. No backport has been issued to ESR branches as of advisory publication.

  • Update Firefox to ≥ 150.0 immediately. Firefox 149.x in any sub-revision remains vulnerable.
  • Thunderbird users: update to ≥ 150.0. HTML message rendering shares the affected libxul build.
  • Enterprise deployments unable to update immediately: disable WebRTC via media.peerconnection.enabled = false in user.js or Group Policy to eliminate the RTP attack surface. This does not mitigate the JS engine or Web Codecs paths.
  • For Web Codecs mitigation pre-patch: dom.media.webcodecs.enabled = false in about:config.
  • JIT hardening: enabling javascript.options.ion = false breaks the IC compilation path but introduces severe performance regression and is not recommended for production.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →