A memory corruption bug becomes a working exploit when the attacker can redirect an indirect call, jump or return to code of their choosing. D3-CFI Control Flow Integrity is the MITRE D3FEND countermeasure that removes that freedom: D3FEND defines it as "Enforcing legal control flow transfers during application process execution." It sits in the Harden tactic under Application Hardening. This card explains what CFI checks, which artefacts D3FEND models, how compiler, operating system and hardware implementations differ, how to prove a build and a fleet are really protected, and where CFI stops helping. It is written for application security engineers, build owners and endpoint teams. For the wider picture of ASLR, DEP and CFI together, see the Exploit Mitigations Guide.
Key takeaways
- CFI restricts where execution can go, not what data says. It checks indirect branch targets against an allowed set; data-only attacks that never change control flow pass straight through, as D3FEND itself notes.
- Granularity decides the strength. Coarse schemes (any valid function entry) leave many usable targets; type-based schemes such as Clang's cfi-icall narrow the set but need LTO and more compatibility work.
- Forward edge and backward edge are different controls. Call and jump checks (Control Flow Guard, Clang CFI, IBT, BTI) protect forward edges; shadow stacks protect returns. A complete deployment needs both.
- Coverage gaps are the usual failure. Code built without the flag is not covered by the check, so one legacy library can leave a gap.
- Verify the binary, then the runtime. Check headers and ELF notes in the build pipeline, then confirm the mitigation is actually enforced on the endpoint and that violations are logged.
D3-CFI at a glance
| Field | D3-CFI Control Flow Integrity |
|---|---|
| D3FEND ID | D3-CFI |
| Tactic | Harden |
| Parent technique | Application Hardening (siblings include Application Configuration Hardening, Dead Code Elimination, Exception Handler Pointer Validation, Pointer Authentication, Process Segment Execution Prevention, Segment Address Offset Randomization and Stack Frame Canary Validation) |
| Digital artefacts (D3FEND relations) | enforces Control Flow Policy; monitors Call Stack and Shadow Stack; validates Control Flow Graph and Memory Address |
| What it changes | Indirect calls, jumps and returns are checked against a set of valid targets computed at compile time or load time; a failed check ends the process or raises an exception |
| Typical implementation points | Compiler and linker flags, operating system loader and kernel support, CPU features (shadow stacks, branch target instructions), endpoint exploit protection policy |
| Verification data | Binary headers and ELF notes, per-process mitigation status, exploit protection audit and block events, crash telemetry |
| Effort | Low for a rebuild with coarse CFI; moderate to high for fine-grained schemes and legacy or JIT code |
| Main limitation | Valid but unintended targets and data-only attacks remain possible |
How it works
D3FEND's knowledge base article describes the mechanism: the program is instrumented at compile time or by binary rewriting, a control flow graph defines the legitimate targets for each indirect transfer, and a check runs before the branch is taken. If the target is not in the allowed set, a defensive response such as process termination follows. D3FEND models the allowed subset as a Control Flow Policy, which it defines as the expected and allowed transfers computed from the program's control flow graph.
D3FEND groups implementations into three families, and they map well to what is deployed today:
- Compiler-based CFI: the compiler inserts checks that validate indirect call targets against type or signature constraints. Clang's
-fsanitize=cfifamily is the reference example, with schemes such ascfi-icall(indirect call to a function of the wrong dynamic type),cfi-vcallandcfi-nvcall(calls through an object with the wrong dynamic type) and the cast checks. Clang requires-fltoor-flto=thinfor these schemes;kcfi, used for kernels, does not need LTO and checks only function pointers. - Operating system assisted CFI: the compiler records valid targets and the operating system keeps a table it checks at run time. Microsoft Control Flow Guard (
/guard:cf, with/DYNAMICBASErequired) works this way: the compiler inserts a check before each indirect call and Windows terminates the program if the target is not a verified location. - Hardware-assisted CFI: the CPU enforces it. Intel CET provides shadow stacks (return addresses kept in protected memory) and indirect branch tracking; on Arm, Branch Target Identification marks valid landing pads. GCC exposes these as
-fcf-protection=fullon x86 (branch plus return checks, implemented with CET) and-mbranch-protection=standardon AArch64 (which turns on all branch protection features, includingbtiandpac-ret).
Forward edge versus backward edge
| Edge | What is protected | Examples | Related D3FEND technique |
|---|---|---|---|
| Forward edge | Indirect calls and jumps, virtual calls, jump tables | Clang CFI schemes, Control Flow Guard, Intel IBT, Arm BTI, kCFI | D3-CFI |
| Backward edge | Function returns | CET shadow stack (Windows Hardware-enforced Stack Protection, Linux user shadow stack), Arm pac-ret, Arm guarded control stack (gcs) | D3-CFI monitors Shadow Stack; see also D3-SSC Shadow Stack Comparisons and D3-PAN Pointer Authentication |
ATT&CK techniques and D3-CFI
Relationship type: none in D3FEND data. D3FEND lists no ATT&CK technique mapping for D3-CFI, direct or inferred (checked November 2026). Its artefacts (call stack, control flow graph, memory address) are not ones that ATT&CK techniques are modelled against.
In practice CFI matters wherever exploitation of a memory corruption bug is the step that gives execution. In NHI Mgmt Group's view, not D3FEND's, the most relevant ATT&CK techniques are T1203 Exploitation for Client Execution, T1068 Exploitation for Privilege Escalation and T1211 Exploitation for Stealth, and the closest ATT&CK mitigation is M1050 Exploit Protection. CFI does not prevent the bug; it makes the classic redirection step (return-oriented and jump-oriented programming, function pointer overwrites) fail. See the M1050 Exploit Protection and T1068 Exploitation for Privilege Escalation pages.
Where it is implemented
Platforms are listed alphabetically.
| Platform or toolchain | Forward edge setting | Backward edge setting | Notes from the documentation |
|---|---|---|---|
| Clang / LLVM | -fsanitize=cfi (or individual schemes) with -flto and -fvisibility=hidden; -fsanitize=kcfi for kernels | Use the target's hardware option (see GCC rows, Clang accepts the same flags) | Violations abort by default; -fno-sanitize-trap= prints a diagnostic first. Cross-DSO mode is experimental with an unstable ABI |
| GCC on AArch64 | -mbranch-protection=bti or standard | pac-ret (in standard), gcs for guarded control stack | Default is none |
| GCC on x86 | -fcf-protection=branch | -fcf-protection=return; full enables both | check at LTO link reports mismatched settings across objects |
| Linux kernel (x86-64) | Kernel IBT | User space shadow stack, Kconfig X86_USER_SHADOW_STACK | Per-process status in /proc/<pid>/status (x86_Thread_features); nousershstk disables it |
| Microsoft MSVC and Windows | /guard:cf on compiler and linker, /DYNAMICBASE | Linker /CETCOMPAT (x64) marks images shadow stack compatible; Hardware-enforced Stack Protection in Exploit protection | CFG supported on Windows 10, Windows 11 and Windows Server 2019 and later |
Memory-safe languages remove much of the corruption that CFI defends against, but mixed codebases still call C and C++ libraries, so CFI stays relevant for those components. See the Memory-Safe Languages Guide and the Compiler and Toolchain Hardening Guide, which covers the full flag baseline.
Implementation steps
- Inventory native code: list the C, C++ and assembly components you build and the third-party native libraries you ship. Note JIT engines and plugins, which need special handling.
- Turn on coarse CFI everywhere first:
/guard:cfwith/DYNAMICBASEon Windows,-fcf-protection=fullon x86 Linux and-mbranch-protection=standardon AArch64. These rarely need source changes. - Add backward edge protection: link with
/CETCOMPATand enable shadow stacks where the hardware supports them. On Windows, use the audit-only mode of Hardware-enforced stack protection first to measure compatibility. - Pilot fine-grained CFI on high-exposure code: parsers, network-facing services and browser-like components are the candidates for Clang
cfi-icallandcfi-vcall. Build with diagnostics (-fno-sanitize-trap=) in test, run the full test suite and fuzzing, then switch to trapping in release. - Keep exemptions small and recorded: Clang ignorelists and
nocf_checkattributes are legitimate, but each one is a hole. Track them like hardening exceptions with an owner and a review date. - Roll back per component: if a release crashes on a CFI check, revert that component's flag (or disable the Windows mitigation for that one executable) rather than turning CFI off fleet wide, and treat the crash as a possible bug or attack until proven otherwise.
How to verify it works
| Check | How | Expected result |
|---|---|---|
| Windows image built with CFG | dumpbin /headers /loadconfig app.exe | Guard in DLL characteristics; Guard Flags show CF Instrumented and FID table present |
| Linux ELF marked for shadow stack | readelf -n <binary> and look for SHSTK | The property note is present on the binary and every shared library it loads |
| Hardware and kernel support | /proc/cpuinfo flags | user_shstk listed |
| Enforced at run time | /proc/<pid>/status on Linux; Exploit protection settings or audit events on Windows | x86_Thread_features: shstk; mitigation shown as on for the process |
| Checks actually trap | A unit test in a test build that makes an indirect call through a mistyped function pointer (Clang cfi-icall) | The process aborts or prints the CFI diagnostic |
Example metrics (examples, not standards): percentage of shipped native binaries with forward edge CFI, percentage with shadow stack compatibility, number of open ignorelist or nocf_check exemptions, and CFI-related crashes per release, each investigated.
Limits, bypasses and failure patterns
- Valid but unintended targets: D3FEND notes that redirecting execution to an allowed target inside the graph can still lead to exploitation. Coarse CFI, where any address-taken function is valid, leaves the largest set.
- Data-only attacks: logic corruption and data-oriented programming change behaviour without changing control flow. Pair CFI with bounds checking, canaries and memory-safe code.
- Unprotected modules: Microsoft states that a mixture of CFG and non-CFG code runs, but failing to enable CFG for all code can open gaps in the protection. Strict CFG in Exploit protection blocks loading of non-CFG binaries, at a compatibility cost.
- JIT and dynamic code: D3FEND lists JIT workflows as a compatibility limit. Windows treats committed executable pages as valid targets by default unless the JIT uses
PAGE_TARGETS_INVALIDorPAGE_TARGETS_NO_UPDATE. - Cross-library calls: Clang treats calls across shared library boundaries as calls to a non-CFI callee unless the experimental cross-DSO mode is used.
- No audit mode for CFG: Microsoft states Control Flow Guard has no audit mode, so compatibility must be tested in the build and test pipeline, not by observing production.
Framework mapping
| Framework | Reference | Relationship to D3-CFI (NHI Mgmt Group mapping) |
|---|---|---|
| MITRE ATT&CK | M1050 Exploit Protection | Closest mitigation; not a D3FEND link |
| NIST SP 800-53 Rev 5 | SI-16 Memory Protection | Controls that protect memory from unauthorised code execution |
| NIST SP 800-53 Rev 5 | SA-15 Development Process, Standards, and Tools | Hardening flags as a required build standard |
| NIST CSF 2.0 | PR.PS-06 | Secure software development practices integrated into the life cycle |
| ISO/IEC 27001:2022 | Annex A 8.28 Secure coding | Secure build configuration as part of secure coding |
Related countermeasures
- Return protection: D3-SSC Shadow Stack Comparisons and D3-PAN Pointer Authentication.
- Corruption detection: D3-SFCV Stack Frame Canary Validation, D3-PV Pointer Validation and D3-MBT Memory Boundary Tracking.
- Attack surface reduction in the same parent: D3-DCE Dead Code Elimination removes code that could otherwise serve as a target.
For how D3FEND is organised, see the MITRE D3FEND Guide.
Practitioner checklist
- List every native binary and library you ship, including third-party ones.
- Build all of it with forward edge CFI:
/guard:cf,-fcf-protection=fullor-mbranch-protection=standard. - Mark images shadow stack compatible and enable shadow stacks where hardware allows, auditing first on Windows.
- Use fine-grained Clang CFI with LTO for the highest-exposure parsers and services.
- Fail the build when a release binary lacks the expected headers or ELF notes.
- Keep a register of ignorelist and
nocf_checkexemptions with owners. - Collect exploit protection events and CFI crash reports and triage them as security signals.
- Pair CFI with memory safety work: canaries, bounds checks and memory-safe rewrites.
Standards and references
- MITRE D3FEND: Control Flow Integrity (D3-CFI)
- LLVM: Clang Control Flow Integrity
- Microsoft: Control Flow Guard for platform security
- Microsoft: /guard (Enable Control Flow Guard)
- Microsoft: Exploit protection reference
- GCC: Program Instrumentation Options (-fcf-protection)
- GCC: AArch64 Options (-mbranch-protection)
- Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack
Related NHI Mgmt Group resources: Exploit Mitigations Guide · Compiler and Toolchain Hardening Guide · M1050 Exploit Protection · MITRE D3FEND Guide