In February 2019, Microsoft's Matt Miller told a BlueHat IL audience something the company had never said so plainly: across roughly twelve years of security patches, about 70% of the CVEs Microsoft fixed and assigned a number to traced back to memory-safety issues — heap out-of-bounds reads and writes, use-after-free, type confusion, uninitialized memory — almost entirely in C and C++ code. Google's Chromium Security team ran the same autopsy in May 2020 on 912 high- and critical-severity Chromium bugs disclosed since 2015 and landed on the identical number: about 70% were memory-unsafety bugs, and roughly half of those were use-after-free specifically. Two unrelated, multi-billion-line codebases, two independent audits, one converging figure. It was consistent enough that by December 2023, CISA, the NSA, the FBI, and Five Eyes partners jointly published "The Case for Memory Safe Roadmaps," calling memory-safety defects the single most prevalent disclosed vulnerability class in software and asking vendors to publish concrete plans toward memory-safe languages or hardened mitigations. This piece walks through the core memory-safety bug classes that produce that 70% figure, and the concrete tooling — AddressSanitizer, fuzzing, and Rust interop — that actually catches them before they ship.
How prevalent are memory-safety bugs in real-world C/C++ software?
The 70% figure from Microsoft and Chromium isn't a one-off statistic — it's a pattern that holds across codebases with very different threat models, release cadences, and codebases largely written in C and C++ over decades. Microsoft's number spans everything from the Windows kernel to Office; Chromium's spans a browser engine that processes untrusted input (arbitrary web content) by design, which is precisely the workload where memory-safety bugs turn into remote code execution. Both organizations reached the same conclusion independently: static and dynamic analysis, code review, and fuzzing all help, but none of them eliminate the underlying class of bug, because the root cause is a language that trusts the programmer to manage memory correctly on every single line, every time. That's the reasoning CISA cited in its 2023 memory-safe roadmap guidance — not that C/C++ teams are careless, but that manual memory management at this scale statistically produces this outcome regardless of team discipline.
What is a buffer overflow, and why does it still happen in 2026?
A buffer overflow happens when a program writes (or reads) past the boundary of an allocated block of memory — a fixed-size stack array, a heap buffer, a struct field — because the code never checked that the incoming data actually fit. Classic causes include unchecked strcpy and memcpy calls, off-by-one loop bounds, and integer overflows in size calculations that wrap a large length into a small positive number before it's used to allocate a buffer. A stack buffer overflow can overwrite a saved return address and hijack control flow; a heap overflow can corrupt adjacent allocator metadata or neighboring objects, producing effects that only surface later and far from the actual bug. Compilers have added mitigations — stack canaries, _FORTIFY_SOURCE bounds-checked libc wrappers, non-executable stack (NX/DEP), and ASLR — but these are hardening layers that make exploitation harder, not fixes that make the underlying out-of-bounds write impossible. The bug class persists because bounds checking in C/C++ is opt-in, not structural.
What makes use-after-free and double-free so hard to catch by review?
Use-after-free occurs when a program keeps a pointer to a heap object after calling free() on it, then dereferences or writes through that stale pointer — often long after the original allocation site, sometimes across totally different functions or threads, which is exactly why Chromium's audit found it accounted for roughly half of all memory-unsafety bugs it studied. Double-free is the sibling bug: calling free() twice on the same pointer, which corrupts heap allocator metadata and can be leveraged into a controlled write primitive. Both bugs are hard to catch by manual review because ownership of a pointer is implicit in C/C++ — nothing in the language tracks who is responsible for freeing an object or forces every reference to be invalidated when it's freed. In large codebases with deep call graphs, callback-heavy architectures, and reference-counted objects passed across module boundaries, the free and the dangling use can be separated by thousands of lines and multiple files, which is precisely the kind of non-local defect that human code review reliably misses.
How does AddressSanitizer actually catch these bugs before release?
AddressSanitizer (ASan), built into both LLVM/Clang and GCC and enabled with the -fsanitize=address compiler flag, instruments every memory access at compile time and wraps allocations in "red zones" — poisoned regions immediately before and after each buffer. If instrumented code reads or writes into a red zone, ASan aborts execution immediately with a stack trace pointing at both the faulting access and the original allocation site, rather than letting the corruption silently propagate until a crash occurs somewhere unrelated. For use-after-free, ASan's quarantine mechanism keeps freed memory mapped and poisoned instead of immediately returning it to the allocator, so a dangling-pointer dereference is caught at the moment it happens instead of manifesting as unexplained corruption later. The tradeoff is runtime and memory overhead, which is exactly why ASan is a testing and CI tool rather than a production hardening measure — teams run it against their fuzz corpus and test suite, not against a live service, but doing so turns invisible memory corruption into a hard, reproducible, immediately-diagnosable crash.
What role has fuzzing played in finding these bugs at scale?
Fuzzing pairs naturally with sanitizers like ASan: a fuzzer generates huge volumes of malformed or boundary-case input, and the sanitizer turns memory corruption that would otherwise go unnoticed into an instant, actionable crash. Google's OSS-Fuzz service, launched in 2016 partly in response to the Heartbleed vulnerability (CVE-2014-0160) going undetected in OpenSSL for years, continuously fuzzes participating open-source C/C++ projects with libFuzzer and AFL, compiled with ASan and other sanitizers enabled. By 2025, Google reported OSS-Fuzz had surfaced more than 13,000 vulnerabilities and over 50,000 total bugs across more than 1,000 open-source projects — a scale no manual review effort or one-time audit could realistically match, because fuzzing keeps running against every new commit indefinitely rather than checking a codebase once. The lesson for any team maintaining C/C++ code is that memory-safety bugs are found continuously and cheaply by automated fuzzing infrastructure once it's wired into CI — the cost is mostly in initial harness setup, not ongoing compute.
Why are CISA and major vendors pushing Rust interop instead of just better C/C++ tooling?
CISA's December 2023 "Case for Memory Safe Roadmaps," co-published with the NSA, FBI, and Five Eyes partners, didn't ask vendors to simply run more sanitizers and fuzzers — it asked them to publish concrete roadmaps toward memory-safe languages, explicitly naming Rust alongside others, or toward equally rigorous mitigations where a full rewrite isn't feasible. Rust's borrow checker enforces ownership and lifetime rules for memory at compile time, which structurally eliminates use-after-free, double-free, and most buffer overflows in safe Rust code — the same bug classes that ASan and fuzzing exist to hunt down after the fact in C/C++. Because rewriting an entire mature C/C++ codebase is rarely realistic, the practical path most teams take is incremental: introducing Rust at the FFI boundary for new, high-risk, or parser-heavy components — the exact code that handles untrusted input and therefore produces the most memory-safety CVEs — while leaving stable, well-tested C/C++ in place elsewhere. That's a defensive strategy grounded in the same data point this whole class of bug traces back to: the 70% figure isn't a tooling gap, it's a language design gap, and Rust interop is the mitigation that addresses the gap directly rather than working around it.