10 · CWE-843

Type Confusion

An object is allocated as one type. Then the program treats its memory as a different type. The field offsets line up wrong — and where one type stored a benign integer, the other interprets it as a function pointer.

In a strongly-typed language, the compiler enforces that a Foo object is treated only as Foo. Type confusion happens when something breaks that promise: a bug in a JIT, a faulty cast, a missing tag check, an unchecked union.

The result is structural — the same bytes in memory are interpreted as different types by different parts of the program. Type confusion is the single most common vulnerability class in modern browser exploits, because JavaScript engines optimize aggressively and the optimizers are where the bugs live.

The shape

Two classes with the same memory size but different field layouts:

struct ImageBuffer
+0x00widthint
+0x04heightint
+0x08pixelschar*
+0x10padint
struct ScriptHandler
+0x00callbackfn ptr
+0x08argvoid*
+0x10flagsint
+0x14refsint

Both occupy 32 bytes. ImageBuffer.width is at +0x00; ScriptHandler.callback is also at +0x00. If the runtime allocates an ImageBuffer and the attacker sets width = 0x401234, then a buggy cast lets the code interpret the same memory as a ScriptHandlerand now callback is a function pointer to 0x401234. Calling handler->callback(...) jumps to attacker-controlled code.

Where it shows up

1. C / C++ unsafe casts (reinterpret_cast)

void *raw = parse_message(buf); ScriptHandler *handler = (ScriptHandler*)raw; // no runtime check handler->callback(handler->arg); // might call anything

2. C unions without a tag

Unions share memory between members. If the code writes to one variant and reads from another without checking which is currently valid, it's type confusion by design.

union Value { int num; char *str; }; // somewhere wrote val.num = attacker_input; // later reads val.str and dereferences: printf("%s", val.str); // dereferences attacker-controlled address

3. C++ downcasting

Casting a base class to a derived class without verifying the actual runtime type. If the object is really a different sibling class, fields are at wrong offsets.

Animal *a = get_animal(); Dog *d = (Dog*)a; // what if it's really a Cat? d->wag_tail(); // invokes wrong vtable slot or worse

The correct C++ form — dynamic_cast<Dog*>(a) — returns nullptr if a isn't really a Dog. But dynamic_cast requires RTTI and is slower, so codebases often use the unchecked C-style cast.

4. JavaScript engine JITs

This is the home turf of modern type-confusion exploitation. V8, JavaScriptCore, SpiderMonkey all watch how variables are used and compile specialized fast paths assuming consistent types. If the engine's guard on the type is missing or wrong, the JIT's compiled code executes type-specialized operations on the wrong type. The exploit shape is:

  1. Train the JIT to assume x is always an integer array.
  2. Trigger the compiled fast path.
  3. During execution, secretly replace x with an object array (Type Confusion!).
  4. The compiled code does integer arithmetic on the object pointers, treating object addresses as int values.
  5. Result: arbitrary read/write primitive, full RCE within the renderer process.

Almost every Pwn2Own browser win in the last decade has been a type confusion in the JIT. Both the incommensurable — the JIT is incredibly complex — and the incentive — speed matters — make this fertile ground for bugs.

Famous incidents

YearCVEWhat it was
2018CVE-2018-4878Adobe Flash Player. Type confusion exploited in the wild by North Korean state actors against South Korean targets.
2021CVE-2021-21193Chrome V8 type confusion. Exploited in-the-wild zero day.
2022CVE-2022-1096Chrome V8. Type confusion targeting JIT compilation. Used by NSO Group for targeted attacks.
2023CVE-2023-4863libwebp. Started as integer overflow but the chain involved type confusion through structure overlap. Mass-patched at Apple, Google, Microsoft, Mozilla within a week.
2024CVE-2024-0519V8 out-of-bounds read. Patched same day as disclosure; in-the-wild exploitation confirmed.

Defenses

1. Use safe casts

In C++, prefer dynamic_cast with a null check over C-style casts. Pay the RTTI overhead. In codebases that have explicitly opted out of RTTI for size, use a custom tagged-type system: every object carries a type ID, every cast checks the ID before reinterpreting.

2. Tag your unions

Replace bare C unions with tagged structures: a discriminator field that says which variant is current. Standardized as std::variant<A,B,C> in C++17, which throws on invalid access. Rust's enums encode the tag in the type system — type confusion of this form is impossible.

3. Sanitize during testing

-fsanitize=cfi (Control Flow Integrity) catches some type-confusion-derived indirect calls at runtime. UBSan with -fsanitize=vptr catches polymorphic class confusion. Use in CI; fix every report.

4. Compile-time static analysis

Clang's -Wcast-align, -Wstrict-aliasing; tools like CodeQL and Semmle can find suspicious casts. Not a silver bullet but reduces the surface.

5. Memory-safe languages

Rust's type system makes the structural form of this bug impossible — every cast must be explicit and checked. Go has interface assertion (x.(*Foo)) that returns a second boolean. C#, Java, Swift all type-check at runtime.

Why the JavaScript JITs keep losing. JIT compilers are themselves code generators that produce native machine code at runtime. The native code they produce assumes specific types based on profiling. A bug in the type-guard logic means the generated code runs without the safety net. Rewriting V8 in a memory-safe language doesn't fix the problem — the generated code is the vulnerability surface. Google's response has been site isolation, sandboxing, and faster patching, not language change.
The point

Type confusion is what happens when the same bytes get interpreted as two different types. It's structural: an unsafe cast, a missing tag check, a JIT optimization that ran without its guard. The exploits aren't subtle — you have one type's field at the same offset as the other type's function pointer, and now you control execution.

Defense is type-system discipline: explicit checked casts, tagged unions, modern C++ std::variant, or jumping to a memory-safe language entirely. In codebases where that's not possible — JIT compilers, kernel code — rigorous testing, fuzzing, and sandboxing carry the load.

References

Formatted in APA 7.

  1. MITRE. (2024). CWE-843: Access of resource using incompatible type ('type confusion'). Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/843.html
  2. Google. (2024). V8 sandbox and the case for type-aware fuzzing. Google Project Zero. https://googleprojectzero.blogspot.com/
  3. Haller, I., Slowinska, A., Neugschwandtner, M., & Bos, H. (2013). Dowsing for overflows: A guided fuzzer to find buffer boundary violations. USENIX Security Symposium. https://www.usenix.org/system/files/conference/usenixsecurity13/sec13-paper_haller.pdf
  4. Stroustrup, B. (2013). The C++ programming language (4th ed.). Addison-Wesley.
  5. National Institute of Standards and Technology. (2022). CVE-2022-1096 detail (Chrome V8). National Vulnerability Database. https://nvd.nist.gov/vuln/detail/CVE-2022-1096