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.
Firefox and Thunderbird users should update immediately. Researchers discovered serious flaws in versions 149 of both programs that could let hackers take over your computer entirely.
Here's what went wrong. These applications have bugs in their memory management — think of it like a librarian who forgets to lock filing cabinets. Hackers can exploit these gaps to inject malicious code directly into your system. Once in, they get the same access you have: your passwords, emails, files, everything.
The attack is surprisingly simple in theory. You'd just need to visit a malicious website or open a crafted email attachment while using the vulnerable version. The attacker doesn't need your password or any special access — the software flaw does all the heavy lifting for them.
The good news: Firefox 150 and the next version of Thunderbird fixed these problems. No one has reported active attacks yet, but that's not a reason to wait. Hackers actively scan for these kinds of vulnerabilities the moment they're announced.
Who's most at risk? Anyone still running Firefox 149 or Thunderbird 149, especially people who visit untrusted websites or receive emails from unfamiliar senders.
Here's what you should do right now:
Update Firefox immediately. Go to Menu, then Help, then About Firefox. It'll automatically check for updates and prompt you to restart.
Update Thunderbird the same way if you use it. Menu, Help, About Thunderbird.
If you can't update today, avoid clicking links in emails or visiting unfamiliar websites until you do.
These updates take minutes but protect you from complete system compromise. Don't put it off.
Want the full technical analysis? Click "Technical" above.
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.
WebRTC — libwebrtc integration layer; buffer ownership during ICE/DTLS negotiation and RTP packetization (Bugs 2021769, 2027499, 2027501).
Web Codecs — VideoDecoder/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.
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.
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 path — VideoDecoder.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.