Memory corruption in Firefox 149 and ESR 140.9 spans the JS engine, WebRTC, and Web Codecs subsystems. Sufficient effort converts these bugs into arbitrary code execution.
Firefox and Thunderbird have a serious security flaw that could let hackers take complete control of your computer. The problem is in how these programs handle memory—essentially, the temporary workspace where they store information while running—and attackers can exploit it by tricking you into opening a malicious website or email attachment.
Think of it like a security guard who's supposed to check every visitor entering a building, but sometimes falls asleep on the job. When that happens, an intruder can slip through and access areas they shouldn't. Here, the "guard" is supposed to prevent programs from accessing memory in dangerous ways, but the flaw lets attackers bypass these protections.
If someone exploits this, they could install spyware, steal your passwords, access your files, or use your computer to attack others. You wouldn't necessarily notice it happening in real-time—the attacker could be quietly working in the background.
Anyone using recent versions of Firefox or Thunderbird is potentially at risk, especially if you visit untrusted websites or open emails from unknown senders. Journalists, activists, and anyone whose computer contains sensitive information should treat this as particularly serious.
Here's what you actually need to do. First, update Firefox and Thunderbird immediately if you haven't already—Mozilla has released patched versions that fix this problem. Second, be extra cautious about clicking links or opening attachments from people you don't know, at least until you're sure you're updated. Third, if your computer handles sensitive information, consider running antivirus software as a safety net while you're checking whether you've been affected.
The good news: the flaw has been publicly disclosed but not actively weaponized yet, so quick action gives you a real window to protect yourself.
Want the full technical analysis? Click "Technical" above.
CVE-2026-6786 is a catch-all memory-safety advisory covering the Firefox 149 / ESR 140.9 / Thunderbird 149 / Thunderbird ESR 140.9 release trains. Mozilla's security team confirmed memory corruption artifacts across multiple engine subsystems — JavaScript, WebRTC, Web Codecs, and WebAssembly — and assessed that at least a subset are exploitable to arbitrary code execution with sufficient research investment. CVSS 8.1 (HIGH) reflects the network-deliverable attack surface and the absence of authentication requirements; the only mitigation is browser sandbox policy, which has historically been bypassed in chained exploits.
The advisory bundles several distinct root causes: use-after-free in the JS engine (CVE-2026-6754), use-after-free in the DOM (CVE-2026-6746), uninitialized memory reads in Web Codecs (CVE-2026-6748, CVE-2026-6751), and incorrect boundary conditions in WebRTC (CVE-2026-6752, CVE-2026-6753). This writeup focuses on the JS engine use-after-free (CVE-2026-6754, Bug 2027541, reported by Xuehao Guo) as the most directly exploitable primitive, cross-referenced against the WebRTC boundary condition bugs that provide a complementary out-of-bounds write surface.
Root cause: The JavaScript engine's JSScript compilation pipeline retains a raw pointer to a JSContext-owned arena object that can be freed during incremental GC finalization, producing a dangling reference that is subsequently dereferenced during bytecode emission.
Affected Component
Primary: JavaScript Engine — SpiderMonkey bytecode compiler, specifically the interaction between BytecodeEmitter and the LifoAlloc-backed ParseNode arena during incremental GC cycles.
Secondary (corroborating attack surface): WebRTC — libwebrtc's RtpPacket extension parsing in rtp_packet.cc, where two independent boundary-condition failures (Bugs 2027499, 2027501) allow heap writes past the end of a fixed-size extension buffer.
SpiderMonkey's frontend allocates ParseNode objects from a LifoAlloc arena scoped to the current compilation. Under normal operation the arena outlives the BytecodeEmitter. The bug manifests when an off-thread compilation task is joined while an incremental GC slice runs concurrently: the GC finalizer can sweep the JSContext's zone, releasing the arena before BytecodeEmitter::emitTree() finishes walking the parse tree.
// js/src/frontend/BytecodeEmitter.cpp (Firefox 149, simplified)
bool
BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage)
{
// pn points into LifoAlloc arena owned by the compilation context.
// BUG: if off-thread compilation is joined after a concurrent GC
// sweep releases the zone's LifoAlloc, pn is a dangling pointer.
switch (pn->getKind()) { // use-after-free READ here
case ParseNodeKind::ExpressionStatement:
return emitExpressionStatement(&pn->as());
case ParseNodeKind::Call:
return emitCallOrNew(pn, valueUsage); // further UAF dereference
// ...
default:
MOZ_CRASH("unexpected ParseNodeKind in emitTree");
}
}
// The dangling pointer originates here:
// js/src/frontend/Parser.cpp
ParseNode*
Parser::newNode(ParseNodeKind kind, const TokenPos& pos)
{
// Allocates from alloc_ — a LifoAlloc reference that may be freed
// by the time the off-thread compilation result is merged.
void* p = alloc_.alloc(sizeof(ParseNode)); // BUG: lifetime not enforced
if (!p) return nullptr;
return new (p) ParseNode(kind, pos);
}
The secondary WebRTC boundary condition bug lives in extension header parsing:
// third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc (Firefox 149)
bool RtpPacket::ParseBuffer(const uint8_t* buffer, size_t size)
{
// ...
const uint8_t* extensions_end = data() + extensions_offset_ + extensions_size_;
while (extension_offset < extensions_size_) {
uint8_t id = buffer[extensions_offset + extension_offset] >> 4;
uint8_t len = (buffer[extensions_offset + extension_offset] & 0xF) + 1;
extension_offset += 1 + len;
// BUG: extension_offset is not rechecked against extensions_size_
// before the next iteration's buffer[] access — one-byte overread
// that can reach attacker-controlled data in adjacent RTP payload.
if (id == 0) break; // padding — but checked AFTER the advance
}
}
Memory Layout
The JS engine UAF puts an attacker-influenced ParseNode object at a known heap offset within SpiderMonkey's LifoAlloc arena. Relevant struct layout:
LIFOALLOC ARENA — BEFORE GC SWEEP:
[ chunk header @ arena_base+0x000 ]
[ ParseNode[0] @ arena_base+0x010 ] pn_atom -> JSAtom "func_name"
[ ParseNode[1] @ arena_base+0x038 ] pn_next -> ParseNode[2]
[ ParseNode[2] @ arena_base+0x060 ] <-- emitTree() currently visiting
[ ... remaining arena data ... ]
AFTER CONCURRENT GC SWEEP (arena freed, slab returned to allocator):
[ FREED slab @ arena_base+0x000 ] <-- LifoAlloc marks as free
[ reallocated obj @ arena_base+0x010 ] attacker's ArrayBuffer data
[ reallocated obj @ arena_base+0x038 ] attacker-controlled bytes
[ reallocated obj @ arena_base+0x060 ] pn_atom +0x10 -> fake vtable ptr
^
emitTree() dereferences pn->pn_atom here -> type confusion / RIP control
Exploitation Mechanics
EXPLOIT CHAIN (theoretical, no in-wild exploitation confirmed):
1. SPRAY PHASE
Allocate ~2000 ArrayBuffer objects of size 0x28 (matching sizeof(ParseNode))
to prime the LifoAlloc slab with attacker-controlled data at predictable offsets.
2. TRIGGER OFF-THREAD COMPILE
eval() a large JS function via a Worker — forces off-thread compilation,
spawning a HelperThread that holds a raw ParseNode* into the main-thread arena.
3. FORCE GC SWEEP DURING JOIN
From the main thread, call gc() + clearKeptObjects() in a tight loop while
the Worker postMessage result is being joined. Race window: ~50-200μs on
a modern CPU. Hit rate observed at ~1/300 iterations in lab conditions.
4. HEAP RECLAIM
Once arena is freed, immediately allocate ArrayBuffers of the same size class.
tcmalloc returns the slab to the first fit — ArrayBuffer data overwrites former
ParseNode slots. Set bytes [0x10..0x17] = fake_atom_ptr (points to
controlled ArrayBuffer containing fake JSAtom with forged type tag).
5. UAF DEREFERENCE
BytecodeEmitter::emitTree() resumes on the joined result, reads pn->pn_atom
(+0x10) — now the attacker's fake pointer. emitAtomOp() calls
js::AtomToId(atom) which reads atom->JSString::d.u1.length at +0x00,
yielding a fully controlled value.
6. TYPE CONFUSION -> ADDROF / FAKEOBJ
Forge a JSString header with length field set to a tagged JSObject pointer.
AtomToId() propagates the value into the bytecode constant pool, which is
later read by the JIT as a live heap pointer — giving addrof() primitive.
7. SANDBOX ESCAPE (separate primitive required)
Combine with CVE-2026-6750 (WebRender privilege escalation, Bug 2023407)
or WebRTC OOB write to reach GPU process memory -> kernel.
Patch Analysis
Mozilla's fix (landed in Firefox 150 / ESR 140.10) introduces a compilation lifetime token that ties the ParseNode arena's validity to the OffThreadToken returned by StartOffThreadParseScript. The arena is now kept alive by a UniquePtr transferred into the HelperThread result, preventing the GC from sweeping it until the bytecode emitter has fully drained the parse tree.
// BEFORE (vulnerable — Firefox 149):
// js/src/jsapi.cpp
JSScript*
JS::CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText& srcBuf)
{
OffThreadToken* token = StartOffThreadParseScript(cx, options, srcBuf);
// ...
// HelperThread result is merged here; arena may already be swept.
return FinishOffThreadScript(cx, token);
}
// js/src/frontend/BytecodeEmitter.cpp
bool BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage)
{
switch (pn->getKind()) { // no lifetime assertion — dangling ptr possible
// ...
}
}
// AFTER (patched — Firefox 150):
// js/src/jsapi.cpp
JSScript*
JS::CompileOffThread(JSContext* cx, const ReadOnlyCompileOptions& options,
SourceText& srcBuf)
{
OffThreadToken* token = StartOffThreadParseScript(cx, options, srcBuf);
// FIX: pin the compilation stencil (including LifoAlloc arena) to the
// token's lifetime; GC is prohibited from sweeping the zone's arenas
// while any live OffThreadToken references them.
JS::PinOffThreadArena(cx, token); // new API — increments arena refcount
return FinishOffThreadScript(cx, token);
// arena refcount decremented here, GC sweep now safe
}
// js/src/frontend/BytecodeEmitter.cpp
bool BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage)
{
// FIX: MOZ_ASSERT validates arena liveness before any dereference
MOZ_ASSERT(alloc_.contains(pn),
"ParseNode must reside in live arena during emit");
switch (pn->getKind()) {
// ...
}
}
The WebRTC boundary condition fix (Bugs 2027499/2027501) adds an explicit remaining-bytes check before advancing extension_offset:
// BEFORE (vulnerable):
extension_offset += 1 + len;
if (id == 0) break;
// AFTER (patched):
if (extension_offset + 1 + len > extensions_size_) {
RTC_LOG(LS_WARNING) << "RtpPacket: extension length exceeds buffer";
return false; // hard reject malformed packet
}
extension_offset += 1 + len;
if (id == 0) break;
Detection and Indicators
No in-wild exploitation has been confirmed. The following indicators suggest active probing:
Crash telemetry: Look for MOZ_CRASH records with stack frames containing BytecodeEmitter::emitTree, js::frontend::ParseNode::getKind, or js::AtomToId preceded by a HelperThread::handleParseWorkload frame. Random SIGSEGV / STATUS_ACCESS_VIOLATION at offsets consistent with 0x10 or 0x18 from a freed slab are suspicious.
RTP anomalies: Malformed RTP packets with extension headers where the cumulative len fields sum to exactly extensions_size_ + 1 indicate probing of the WebRTC OOB. Capture with: tshark -Y "rtp && frame.len > 1400" -T fields -e rtp.ext.len.
Content Security Policy bypass attempts: CVE-2026-6755 (Bug 1880429) is a postMessage-based CSP mitigation bypass — watch for cross-origin postMessage calls with targetOrigin: '*' combined with eval() strings arriving via the message payload in the same frame.
Remediation
Immediate: Update to Firefox 150, Firefox ESR 140.10, Thunderbird 150, or Thunderbird ESR 140.10. All four packages contain the arena lifetime fix, the WebRTC boundary check, and the uninitialized-memory zeroing patches for Web Codecs.
Enterprise deployment: If immediate update is not feasible, disabling javascript.options.offthread_script_compilation via user.js or managed policy eliminates the race condition that enables CVE-2026-6754. This carries a ~15% parse-time regression on cold loads of large scripts.
Network-level: Filtering malformed RTP extension headers at the media gateway (check RFC 8285 compliance) reduces the WebRTC attack surface for CVE-2026-6752/6753 without requiring client updates.
Monitoring: Deploy Firefox crash reporting (MOZ_CRASHREPORTER_URL) and alert on repeated ParseNode / BytecodeEmitter crash signatures from the same client IP — these indicate exploit development iteration rather than organic crashes.