Breaking Control Flow Integrity by Abusing Modern C++
Security researchers reveal how transient execution gadgets bypass modern hardware and compiler-level control flow integrity protections.
Summary
## Overview
In "Breaking Control Flow Integrity by Abusing Modern C++", Marcos Bajo and Christian Rossow from CISPA Helmholz Center for Information Security present research on evading modern Control Flow Integrity (CFI) defenses by targeting modern C++ coroutines. While CFI techniques (such as Intel CET, Control Flow Guard, and Clang/LLVM CFI) were introduced to eliminate code-reuse techniques like Return-Oriented Programming (ROP) by restricting control flow transitions along a pre-computed Control Flow Graph (CFG), the authors demonstrate that the internal mechanics of stackless C++ coroutines introduce new exploit primitives.
## Background on User-Space CFI Defenses
The talk examines common modern user-space CFI implementations and their operational constraints:
- **Intel CET (Control-flow Enforcement Technology):** A hardware-assisted, coarse-grained defense featuring two components: - *Shadow Stack:* Stored in isolated memory inaccessible to standard user space. It mirrors return addresses and raises a fault if the normal stack return address deviates from the shadow copy. - *Indirect Branch Tracking (IBT):* Enforces landing pads via the `ENDBR` instruction, requiring indirect jumps to land only at valid target entry points. - **Control Flow Guard (CFG):** Microsoft Windows' software-based coarse-grained implementation (used instead of IBT). It replaces indirect calls with direct calls to a validation routine that checks function targets against a bitmap (e.g., verifying the `10` pattern that denotes a valid function beginning). - **Fine-Grained CFI (e.g., LLVM CFI):** Enforces dynamic type checking at compile time by validating indirect calls against exact function prototypes (matching return types and parameter signatures).
## Mechanics of Modern C++ Coroutines
Modern C++ coroutines are stackless functions characterized by the ability to suspend and resume execution across distinct suspension points.
### Core Components & Operators - **Keywords/Operators:** Coroutines require one of three operators: `co_yield` (suspends and returns a value), `co_return` (terminates the coroutine and returns a final value), or `co_await` (evaluates an awaitable object to manage asynchronous tasks or cooperative multitasking). - **Task & Handle:** The coroutine state is encapsulated in a top-level `task` object containing a `coroutine_handle` (a pointer allowing operations like `.resume()` or `.destroy()`). - **Promise Object:** A shared data structure where coroutine return values and custom suspension lifecycle hooks reside.
### Memory Layout & Internal Stubs When compiled, three runtime stub routines are generated: 1. **Creation Stub:** Invokes `malloc` to allocate the heap-resident **coroutine frame** and initializes function pointers. 2. **Resume Stub:** Acts like a switch statement reading a `coroutine index` (an integer identifying the exact suspension point to jump to) and executes the coroutine's body. 3. **Destroy Stub:** Frees the heap-allocated coroutine frame via a call to `free`.
Because C++ coroutines are stackless, local variables do not live on the CPU stack managed by `RSP`/`RBP`; rather, the entire execution context is stored directly in the heap within the **coroutine frame**. This structure contains: - The **resume pointer** (invoked via an indirect call to the resume stub). - The **destroy pointer** (invoked via an indirect call to the destroy stub). - The **promise object** and coroutine parameters. - Local variables (heap-stored values or pointers to other heap allocations). - The **coroutine index** integer determining the resumption target.
## Exploitation Primitives & Threat Model
The research leverages a standard threat model aligned with existing CFI assumptions: - The attacker utilizes an ASLR bypass or information leak to identify function addresses. - The attacker possesses a memory corruption primitive (such as an arbitrary write or heap corruption) to manipulate memory structures.
By corrupting heap-allocated coroutine frames—such as the indirect resume/destroy pointers, the coroutine index, or embedded variable pointers—attackers can manipulate program state and control flow. Because operations like `co_await` trigger `await_suspend` and indirect calls to resume stubs that are recognized as legitimate CFG transitions by both hardware and software CFI implementations, coroutines provide an avenue to subvert control flow constraints without violating CFI entry-point and prototype validation rules.
Related talks
Briefings·28 min
Lukas Maar · Lukas Giner
Presents a novel timing side-channel attack on the TLB, combined with kernel allocator massaging, to derandomize the location of security-critical kernel objects in the latest Linux kernel.
Briefings·38 min
Connor McGarr
Virtual Secure Mode, or VSM, on Windows marked the most significant leap in security innovation in quite some time, allowing the hypervisor to provide unprecedented protection to the Windows OS.
Black Hat·57 min
Presenters detail vulnerability discovery and exploitation in USA 2012 - The Subway Line 8: Exploitation of Windows 8 Metro Style Apps.


