How V8 Type Confusion Bugs Turn JavaScript Into Memory Corruption
πŸ›‘οΈ Security Intermediate 6 min read

How V8 Type Confusion Bugs Turn JavaScript Into Memory Corruption

Why V8's optimizing compilers keep producing type confusion bugs, how one becomes addrof and fakeobj primitives, and where the renderer sandbox actually stops an attacker.

Published: September 5, 2026 β€’ Updated: September 5, 2026
v8type confusionbrowser exploitationjavascript

Every few weeks a browser vendor ships a fix described as "type confusionType ConfusionπŸ›‘οΈA memory-safety bug (CWE-843) in which code accesses a resource using an incompatible type, such as reading a pointer as an integer. In JavaScript engines it typically arises when optimized code trusts an assumption about an object's shape that a callback has since invalidated, as in CVE-2026-85046. in V8" with a note that an exploitExploitπŸ›‘οΈCode or technique that takes advantage of a vulnerability to cause unintended behavior, such as gaining unauthorized access. exists in the wild. The September 2026 Chrome zero-dayZero-DayπŸ›‘οΈA security vulnerability that is exploited or publicly disclosed before the software vendor can release a patchPatchπŸ›‘οΈA software update that fixes security vulnerabilities, bugs, or adds improvements to an existing program., giving developers 'zero days' to fix it. CVE-2026-85046 is the latest, and the researcher who found it published enough detail to make it a good teaching case. This article explains what a type confusion bug is, why V8's optimizing compilers keep producing them, how one becomes memory corruption, and where the browser's defenses actually stop an attacker.

What "type confusion" means in a JavaScript engine

JavaScript has no static types, so an engine has to guess. V8 attaches hidden metadataMetadataπŸ“–Data about dataβ€”like email timestamps, file sizes, or location tags on photos. to every object, including a map that records the object's shape and, for arrays, an elements kind. The elements kind is a promise about what the backing store contains: PACKED_SMI_ELEMENTS means every slot is a small integer stored directly, PACKED_ELEMENTS means slots may hold tagged pointers to heap objects, and there are double and holey variants of each.

That promise is what makes optimized code fast. If the compiler knows an array holds only small integers, it can read a slot and use the bits as a number with no checks. If the promise is broken, the same code reads a pointer as an integer, or writes an integer over a pointer. That mismatch between what the compiled code believes and what memory contains is type confusion, catalogued as CWE-843.

Why the JIT is where these bugs live

V8 runs code in tiers. The interpreter, Ignition, is slow but safe because it checks types on every operation. Hot functions are handed to the optimizing compilers, Maglev and Turbofan, which generate machine code based on assumptions gathered from earlier executions. Each assumption is protected by a guard. If a guard fails, the engine deoptimizes back to the interpreter.

Bugs appear when a guard is missing, too weak, or placed before something that can change the world. User-supplied callbacks are the classic problem. When a built-in such as Array.prototype.sort is inlined, the compiler must assume that the comparator function the page passed in can do anything, including mutate the very array being sorted. The safe response is to re-check every assumption after the callback returns.

In CVE-2026-85046, according to Salvatore Gulizia's write-up, Maglev's inlined sort checked after the comparator that the array's map was still within an accepted set, rather than checking that it was the same map as before. Both PACKED_SMI_ELEMENTS and PACKED_ELEMENTS were in the set, so a transition between them slipped through. Calling Array.prototype.fill inside the comparator migrated the map backwards to the integer kind while the backing store still held object pointers. The compiled sort then continued with integer semantics over pointer data. Gulizia noted the same weakness in Turbofan.

From a wrong assumption to arbitrary read and write

Exploit developers reduce every JavaScript engine bug to two primitives, and the write-up for this bug follows the pattern exactly.

The first is addrof: learn where an object lives. If code treats a pointer slot as an integer and hands the value back to script, the page now knows a heap address. Modern V8 compresses pointers to 32-bit offsets within a heap cage, so the leak is an offset, but that is all the next step needs.

The second is fakeobj: make the engine treat attacker-controlled bytes as a real object. Here the write-up uses Array.prototype.unshift. Because the compiler believed it was shifting integers, it moved the slots without the write barrier the garbage collector relies on to track pointer writes. Pointers moved without the collector's knowledge, and the attacker could place a crafted value where the engine would later dereference it as an object.

With both primitives, the attacker builds a fake ArrayBuffer or typed array whose backing pointer and length they control, and from that point any address in the JavaScript heap can be read or written. That is what the CVE text means by "execute arbitrary code inside the sandbox": the attacker can corrupt JIT-generated code or WebAssembly memory inside the renderer processRenderer ProcessπŸ›‘οΈThe sandboxed browser process that parses HTML, runs page JavaScript and paints content. It has no direct file, network or process-creation rights and reaches them only through IPC to the browser process, so code execution inside it is contained until an attacker also escapes the sandbox. and run their own instructions there.

What the sandbox does and does not stop

The renderer process that runs page JavaScript is deliberately weak. On Windows it runs at low integrity with a restricted token and, on recent builds, inside an AppContainer; on Linux it is confined by seccomp-bpf and namespaces; on macOS by the Seatbelt sandbox. It cannot open files, talk to the network directly, or spawn processes. Everything it needs goes through IPC to the privileged browser process.

So a V8 bug alone gives an attacker code execution in a box. That is why the CVSS vector for CVE-2026-85046 has scope Unchanged and why the CVE description says "inside the sandbox". To reach the operating system an attacker needs a second bug, a sandbox escapeSandbox EscapeπŸ›‘οΈA second-stage exploit that breaks out of a restricted process, such as a browser renderer, into the more privileged browser process or the operating system. In-the-wild browser attacks usually chain a JavaScript-engine bug with a sandbox escape, which is why a renderer-only bug is rated as scope unchanged in CVSS., typically in the browser process's IPC handling, in a GPU or audio service, or in the OS kernel. In-the-wild browser exploit chains almost always pair the two, which is one reason vendors stay quiet about exploitation details: the second bug may not be fixed yet.

There is also V8's own sandbox, a newer in-process boundary that tries to keep heap corruption from reaching raw memory outside the JavaScript heap. It raises the cost of exploitation but is not a security boundary vendors will guarantee, and it does not change patch urgency.

Why the bugs keep coming

Three structural reasons, none of which will change soon:

  • Speculative optimization is the product. Every guard the compiler drops is a measurable speed gain, and every callback boundary is a place where the world can change under the compiler's feet.
  • The attack surfaceAttack SurfaceπŸ›‘οΈThe sum of all points where an unauthorized user could attempt to enter or extract data from a system: exposed services, interfaces, accounts, and integrations. Reducing attack surface means removing reachability, not just patching. is enormous and reachable by anyone. A single ad slot, a compromised site or a watering-hole page can deliver JavaScript to a user with no interaction beyond loading the page.
  • Compilers get rewritten. Maglev is a relatively young tier, and new tiers reproduce old classes of bugs in new code.

The six in-the-wild Chrome zero-days of 2026 include three in V8 and two in graphics code, which matches the long-run pattern.

What this means for defenders

You cannot detect the trigger; it is ordinary JavaScript. You can only make the fix present and running. Two practical consequences follow:

  • A downloaded update is not a deployed update. The vulnerable code runs in memory until the browser process, or the Electron app, is relaunched. The policy side of that is covered in How to Enforce Browser Updates Across a Managed Fleet.
  • One CVE, many binaries. Every product that embeds Chromium (Edge, Brave, Opera, Electron apps, WebView2WebView2πŸ›‘οΈMicrosoft's control for embedding Chromium-based web content inside Windows applications. The Evergreen runtime updates alongside Microsoft Edge, while a Fixed Version runtime is bundled by the application vendor and receives Chromium security fixes only when the vendor ships a new package.) carries its own copy of V8 with its own release train, which is the subject of The Chromium Downstream Problem: Tracking Patches in Forks and Embedded Browsers.

Renderer crashes are the one signal worth watching. Type confusion exploits depend on heap layout and are not perfectly reliable, so a failed attempt often shows up as a crashed tab shortly before something unexpected happens on the host. A spike in renderer crash reports from a single group of users, especially privileged ones, is worth a look during any window when a V8 zero-day is known to be in circulation.