type++: Prohibiting Type Confusion with Inline Type Information

Nicolas Badoux

Network and Distributed System Security (NDSS) Symposium 2025 · Day 3 · Software Security: Code and Compiler

Overview

In the realm of C++ development, the flexibility offered by object-oriented features like inheritance can, paradoxically, introduce significant security vulnerabilities. The talk "type++: Prohibiting Type Confusion with Inline Type Information," delivered by Nicolas Badoux, addresses a critical class of these vulnerabilities known as derived type confusion. This work introduces Type++, a novel C++ dialect meticulously engineered to eliminate these bugs by design, providing robust runtime type checking with remarkably low overhead.

Watch on YouTube · Slides

Key moments

  1. 0:00 Introduction: Understanding C++ derived type confusion
  2. 2:00 Type++: A C++ dialect preventing type confusion
  3. 2:45 How Type++ adds inline runtime type information
  4. 4:10 Handling object layout changes and external libraries
  5. 6:00 Incompatible idiom: sizeof comparisons
  6. 6:45 Incompatible idiom: Implicit placement new
  7. 8:00 Evaluation: Porting effort for C++ projects

type++: Prohibiting Type Confusion with Inline Type Information

Speakers: Nicolas Badoux

Conference: NDSS Symposium

YouTube: https://www.youtube.com/watch?v=eWI2pVwjQCY

Overview

In the realm of C++ development, the flexibility offered by object-oriented features like inheritance can, paradoxically, introduce significant security vulnerabilities. The talk "type++: Prohibiting Type Confusion with Inline Type Information," delivered by Nicolas Badoux, addresses a critical class of these vulnerabilities known as derived type confusion. This work introduces Type++, a novel C++ dialect meticulously engineered to eliminate these bugs by design, providing robust runtime type checking with remarkably low overhead.

Derived type confusion arises when a C++ object is incorrectly interpreted as an object of a different, but related, type within an inheritance hierarchy. Such misinterpretations can lead to arbitrary memory corruption, making them a potent vector for attackers to achieve privilege escalation, information disclosure, or arbitrary code execution. As demonstrated by their prevalence in large, security-sensitive projects like Chromium and Firefox, these vulnerabilities pose a substantial threat to software integrity and user security. Type++ offers a principled, language-level solution to this pervasive problem, moving beyond heuristic-based mitigations to provide a fundamentally safer programming environment.

The significance of Type++ lies in its ability to offer comprehensive protection against derived type confusion without the prohibitive performance penalties often associated with runtime checks. By embedding runtime type information (RTI) directly into relevant objects and carefully managing the C++ Application Binary Interface (ABI), Type++ ensures that every derived cast is validated throughout an object's lifetime. This approach not only secures a vast number of potential cast operations – 90 billion in testing – but does so with an average overhead of less than 1%, making it a highly practical and impactful advancement for C++ security.

Background

▶ Watch: Introduction: Understanding C++ derived type confusion (0:00)

C++ is a powerful object-oriented programming language that supports inheritance, allowing developers to create class hierarchies where child classes inherit properties and behaviors from parent classes. This mechanism enables flexible and generic code, where a function designed to accept a parent object can seamlessly operate on a child object. This process, known as upcasting, is inherently safe because a child object's memory layout always begins with the parent object's attributes, followed by its own.

However, the inverse operation, downcasting, where a parent object is treated as a child object, introduces significant risks. While explicit dynamic_cast operators in C++ provide safe downcasting for polymorphic types (classes with at least one virtual function) by leveraging existing RTI, direct C-style or static_cast downcasts are not inherently safe. A common scenario for derived type confusion occurs when an object, initially a child type, is upcast to its parent, and then subsequently downcast to an incompatible sibling type. For instance, if ChildA is cast to Parent, and then Parent is cast to ChildB, and ChildB has a different memory layout or additional attributes not present in ChildA, attempting to access ChildB-specific fields can lead to memory corruption by reading or writing out-of-bounds.

This problem is not theoretical; derived type confusion bugs have been repeatedly discovered and exploited in critical C++ applications. Projects like Chromium and Firefox, which are foundational to web browsing and rely heavily on complex C++ class hierarchies, have historically suffered from these vulnerabilities. Existing security mitigations, such as LLVM's Control Flow Integrity (CFI) or more specialized sanitizers like Hextype, offer partial protection. Hextype, for example, is reported to protect around 6 billion casts. However, these solutions often come with limitations in coverage or incur higher performance overheads, leading to a trade-off between security and efficiency. The challenge has been to devise a comprehensive, low-overhead solution that fundamentally prevents derived type confusion without requiring extensive manual code audits or crippling performance.

Key Findings

▶ Watch: How Type++ adds inline runtime type information (2:45)

The core contribution of Type++ is its ability to prohibit derived type confusion by design, marking a significant step forward in C++ memory safety. The key findings and contributions of this research are multi-faceted:

First, Type++ introduces a new C++ dialect that transparently embeds inline runtime type information (RTI) into every object involved in derived casts. This crucial modification allows for comprehensive runtime type checks throughout an object's entire lifetime, ensuring the validity of every derived cast operation. Unlike previous approaches that might rely on external metadata or limited runtime checks, Type++'s inline RTI provides a pervasive and robust mechanism for type validation.

Second, Type++ demonstrates exceptional coverage and efficiency compared to existing mitigations. While state-of-the-art sanitizers like Hextype protect approximately 6 billion casts, Type++ is capable of protecting an astounding 90 billion casts. This dramatic increase in protected operations comes with a surprisingly low performance overhead. On average, Type++ introduces less than 1% overhead across various benchmarks, including the spec CPU benchmark 2006 and 2017. Even at its maximum, the overhead remains below 5%, making it highly practical for production environments. This performance characteristic is comparable to, and often better than, other robust mitigations like LLVM CFI, which also aims for low overhead but offers less specific type protection.

Third, the research highlights the practical applicability and bug-finding capabilities of Type++. During its evaluation, Type++ successfully identified 14 previously unknown type confusion vulnerabilities within the spec CPU benchmark. This demonstrates its effectiveness not just as a preventative measure but also as a powerful auditing tool for existing codebases. Furthermore, a case study on the massive Chromium project, involving over 2 million lines of code, showed that 92% of relevant classes could be instrumented, and after fixing 230 lines of code, the protected binary experienced less than 2% overhead on the JavaScript Jetstream 2 benchmark. This real-world application underscores Type++'s potential for securing large, complex software.

Technical Deep Dive

▶ Watch: Handling object layout changes and external libraries (4:10)

Type++ achieves its robust protection against derived type confusion by fundamentally altering how C++ objects carry their type identity, ensuring that this information is available for validation at every critical point. The core mechanism involves extending C++'s existing runtime type information (RTI) to all classes that participate in derived casts, regardless of whether they are polymorphic types.

The first step in this process is the modification of the object layout. For any class involved in a derived cast, Type++ injects a dedicated field to store its RTI directly within the object's memory. This inline approach contrasts with external metadata tables and ensures that type information is always co-located with the object itself. While this change slightly breaks the standard C++ ABI (Application Binary Interface), Type++ provides mechanisms to manage this incompatibility. For interactions with external libraries that expect the standard ABI, Type++ generates wrappers that remove the inline type information before passing objects to external code, and re-inject it upon return. For mixed C/C++ headers, specific macros are provided to make C code aware of the additional field, preventing erroneous memory access.

Crucially, this inline type information must be correctly initialized. Type++ leverages constructors for this purpose. When an object is created, its constructor is responsible for populating the inline RTI field with the correct type identifier. Type++ ensures that a default constructor is defined for every instrumented class; if one is not explicitly provided by the developer, Type++ transparently generates one. For objects allocated using the new keyword, the constructor call is automatic. However, C++ projects often use C-style allocators like malloc, calloc, or realloc, which do not automatically invoke constructors. For these cases, Type++ explicitly injects calls to the constructor immediately after memory allocation. Special care is taken for calloc to ensure all allocated objects have their RTI set, and for realloc to only initialize RTI in the newly allocated memory regions. Furthermore, Type++ includes an allow list for custom memory allocators, such as pool allocators or those used by sanitizers like ASan, to correctly handle scenarios where custom metadata might be added alongside the object.

Type++ also addresses specific C++ idioms that become problematic due to the altered object layout. The first is comparisons with sizeof. Since Type++ adds an extra field for RTI, the sizeof a class in Type++ might be different from its size in standard C++. While this is desirable for malloc calls (which should allocate enough space for the new layout), it can cause issues if sizeof is used in comparisons with scalar values or for specific memory calculations that assume a fixed, standard size. Type++ flags these instances during its static analysis, encouraging developers to modernize such non-portable code segments.

A more subtle and dangerous idiom is what Type++ refers to as "implicit placement new." This occurs when developers define a class (e.g., X) and then create a char array (e.g., Y) of the same size as X, intending to use Y to interchangeably store X or other related types. In Type++, if both X and Y (or the types intended to be stored in Y) are involved in derived casts, they will both receive an inline RTI field. If Y is defined as a char array with sizeof(X), it will effectively have two spaces for type information: one for its own (implicit) type and one for the X object it's intended to hold. This misalignment causes fields to be offset incorrectly when a cast or access occurs, directly leading to type confusion. Type++'s static analysis identifies these patterns, guiding developers toward safer, more explicit modern C++ idioms.

The porting effort for Type++ is remarkably low. On the spec CPU benchmark, which comprises over 2 million lines of C++ code, the static analysis identified 179 warnings, requiring modifications to only 314 lines of code – less than 0.04% of the codebase. An example of a necessary modification involved Blender, which used tagged pointers to store type information in the least significant bits of an address, a practice that conflicts with Type++'s need for aligned addresses and explicit RTI. By adopting Type++, such non-standard, undefined behavior can be replaced with the built-in, robust RTI. The comprehensive static analysis and low modification count underscore Type++'s practical deployability for securing existing C++ projects.

Demo / Proof of Concept

▶ Watch: Incompatible idiom: Implicit placement new (6:45)

While the talk did not feature a live, step-by-step demonstration of Type++ in action during the presentation, the research explicitly states that the project's artifact is publicly available. This artifact has undergone evaluation by the NDSS artifact committee and is hosted on GitHub, allowing interested researchers and developers to access, replicate, and further explore the Type++ implementation and its capabilities. The availability of this artifact serves as the primary proof of concept, enabling independent verification of the claims regarding its protection mechanisms, performance overhead, and the identified vulnerabilities.

Defensive Implications

▶ Watch: Evaluation: Porting effort for C++ projects (8:00)

Type++ presents a compelling new defense strategy against a critical class of vulnerabilities in C++ applications. For defenders, its implications are significant, offering a path to fundamentally enhance the security posture of C++ codebases:

Firstly, proactive vulnerability elimination: Type++ provides a principled, language-level solution to derived type confusion, rather than relying on reactive patching or heuristic-based mitigations. By adopting Type++, organizations can eliminate an entire class of security bugs by design, preventing them from ever reaching production. This shifts the security paradigm from finding and fixing bugs to preventing them at the source, which is a far more robust approach.

Secondly, practical and low-overhead deployment: A major barrier to adopting comprehensive security measures is often performance overhead. Type++'s impressive performance profile – less than 1% average overhead and a maximum of under 5% across diverse benchmarks, including large projects like Chromium – makes it a highly practical solution for production environments. This low overhead means that organizations do not have to compromise between security and application performance, making it a viable option even for high-performance computing or latency-sensitive applications.

Thirdly, improved code quality and maintainability: The static analysis provided by Type++ not only identifies code segments that need adaptation for the dialect but also highlights problematic or non-portable C++ idioms. By guiding developers away from practices like sizeof comparisons on potentially dynamic object layouts or "implicit placement new" with char arrays, Type++ implicitly encourages the adoption of more modern, safer, and more explicit C++ programming patterns. This leads to cleaner, more maintainable code that is less prone to undefined behavior and subtle bugs, regardless of security implications.

Finally, enhanced security for critical software: Given the prevalence of derived type confusion in widely used software like Chromium and Firefox, Type++ offers a robust mechanism to secure these and similar large-scale projects. The successful case study on Chromium, demonstrating the ability to port 92% of classes with minimal code changes and low overhead, indicates that Type++ can be effectively applied to complex, established codebases. This capability is crucial for organizations that maintain extensive C++ software, providing a powerful tool to bolster the security of their critical infrastructure and protect against sophisticated memory corruption attacks.

Key Takeaways

  • Comprehensive Protection: Type++ is a C++ dialect that eliminates derived type confusion by design, protecting 90 billion casts—23 times more than previous state-of-the-art mitigations like Hextype.
  • Inline Runtime Type Information (RTI): It achieves this by embedding RTI directly into objects involved in derived casts, enabling robust runtime type checks throughout an object's lifetime.
  • Minimal Performance Overhead: Type++ boasts an average performance overhead of less than 1% and a maximum of under 5% on benchmarks, making it highly practical for production use.
  • Low Porting Effort: Adopting Type++ requires minimal code modifications; for instance, less than 0.04% of lines needed changes on the spec CPU benchmark, and 230 lines for a significant portion of Chromium.
  • Identifies New Bugs: The system successfully identified 14 new type confusion vulnerabilities in the spec CPU benchmark, demonstrating its effectiveness as a security auditing tool.
  • Encourages Modern C++ Idioms: Type++'s static analysis helps developers identify and replace problematic C++ idioms (e.g., sizeof comparisons, implicit placement new) with safer, more explicit alternatives.

About the Speaker(s)

Nicolas Badoux is a PhD student who presented this work at the NDSS Symposium. His research is a collaborative effort with colleagues from Ro University Bum in Germany and Unist in South Korea, focusing on enhancing the security and reliability of C++ programming through language-level modifications and robust runtime checks.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid systems security research tackling a real, persistent problem in C++ with a language-level solution that actually ships numbers: 90B casts protected, sub-1% average overhead, 14 new bugs found in SPEC CPU, and a credible Chromium case study. Not a world-changer, but this is the kind of careful, principled engineering work that moves the field forward rather than just describing the problem.

Heather Calloway (CISO) — WEAK

Technically solid research on a real vulnerability class, but the talk is aimed squarely at compiler researchers and language toolchain engineers — not the security leaders or defenders who would need to decide whether and how to adopt it. The findings are credible, the overhead numbers are impressive, and the Chromium case study is genuinely useful evidence, but the work stops at the research boundary and never crosses into institutional decision-making.

→ Top-rated talks at Network and Distributed System Security (NDSS) Symposium 2025

All talks from Network and Distributed System Security (NDSS) Symposium 2025