Memory safety bugs in C and C++ code have been the largest single class of serious vulnerabilities in widely used software for well over a decade, and no amount of code review or testing has made them go away. Out-of-bounds writes, use-after-free and out-of-bounds reads still rank in the top ten of the 2025 CWE Top 25, and they are the bugs behind many exploited zero-days and memory leaks of credentials. Memory-safe languages remove most of these bugs by construction, and government agencies in the Five Eyes now ask software manufacturers to publish roadmaps for adopting them. This guide explains what memory safety means, the evidence for it, how to choose a language, how to plan and run a migration without rewriting everything, how to harden the C and C++ that remains, and how to measure progress. It is for application security leads, engineering managers and architects who own native code. General secure development practice is covered in the Secure Coding Guide.
Key takeaways
- The problem is concentrated and measurable: Microsoft, Chromium and Android have each reported memory safety issues as roughly 70 percent of their serious vulnerabilities before adopting safer approaches.
- New code matters most: vulnerabilities cluster in new and recently changed code, so writing new code in a memory-safe language cuts risk long before old code is replaced.
- A roadmap is the expected deliverable: CISA, NSA and partner agencies ask manufacturers for dated phases, a cut-off date for new code in memory-unsafe languages, and a plan for dependencies.
- Interoperability is the migration strategy: NSA and CISA state that adoption does not require a complete rewrite; safe components are introduced behind interfaces into existing C and C++.
- Remaining C and C++ still needs hardening: standard library hardening, compiler flags, sanitisers, fuzzing and hardened allocators reduce exploitability where migration is not yet feasible.
What memory safety means
A language is memory safe when the language and its runtime prevent a program from reading or writing memory it does not own, in code that does not explicitly opt out. The NSA and CISA information sheet of June 2025 describes the built-in protections as bounds checking, memory management and data race prevention, embedded at language level so that memory safety issues are prevented from the outset.
| Bug class | CWE (2025 Top 25 rank) | What happens | How memory-safe languages prevent it |
|---|---|---|---|
| Out-of-bounds write | CWE-787 (5) | Writes past a buffer, corrupting adjacent data or control flow | Bounds-checked indexing and slices |
| Use after free | CWE-416 (7) | Memory reused after release, often attacker-controlled | Garbage collection, reference counting, or compile-time ownership and lifetimes |
| Out-of-bounds read | CWE-125 (8) | Leaks adjacent memory: keys, tokens, passwords | Bounds checks; no reading of uninitialised buffers in safe code |
| Classic buffer overflow | CWE-120 (11) | Copy without checking input size | Length-carrying strings and slices |
| NULL pointer dereference | CWE-476 (13) | Crash, sometimes exploitable | Option or nullable types checked at compile time (Rust, Swift, Kotlin) |
| Stack and heap overflows | CWE-121 (14), CWE-122 (16) | Overflow of stack or heap buffers | Bounds checking |
Memory safety does not remove logic flaws, injection, authorisation errors or misuse of cryptography. It removes one large class, which frees review and testing effort for the rest. See the CWE Top 25 Guide.
The evidence
- Microsoft: about 70 percent of Microsoft CVEs (2006 to 2018 data) were memory safety vulnerabilities, as cited in The Case for Memory Safe Roadmaps.
- Chromium: the Chromium security team reports that around 70 percent of its serious security bugs are memory safety problems, based on 912 high or critical bugs affecting Stable since 2015, and that half of those are use-after-free.
- Mozilla and zero-days: the same agency guide cites 32 of 34 critical or high Mozilla bugs in one analysis, and Google Project Zero's finding that 67 percent of 2021 in-the-wild zero-days were memory safety vulnerabilities.
- Android: Google reported memory safety vulnerabilities falling from 76 percent of Android vulnerabilities in 2019 to 35 percent in 2022 as new code moved to memory-safe languages, and in November 2025 reported them below 20 percent for the first time.
- Defect density and delivery speed: in the same 2025 post Google estimates about 0.2 vulnerabilities per million lines of Rust against close to 1,000 per million lines for historical C and C++ in Android, with a rollback rate about four times lower for medium and large Rust changes and about 25 percent less time in code review.
The Android result is the key planning insight: the vulnerability share fell mainly because new code was written in memory-safe languages, not because old code was rewritten.
Choosing a memory-safe language
The Case for Memory Safe Roadmaps lists C#, Go, Java, Python, Rust and Swift as examples of memory-safe languages. Chromium's memory safety page also names Java, Kotlin, JavaScript, Rust and Swift as options where applicable. The choice is driven mainly by where the code runs and what it must interoperate with.
| Language | Memory management | Good fit | Trade-offs |
|---|---|---|---|
| Rust | Compile-time ownership and borrowing, no garbage collector | Systems code, parsers, codecs, kernels and drivers, embedded, performance-critical libraries replacing C or C++ | Learning curve; unsafe blocks and FFI need review |
| Go | Garbage collected | Network services, infrastructure tooling, CLIs | GC pauses and runtime size; data races must be tested for with the race detector |
| Java and Kotlin | Garbage collected (JVM) | Enterprise services, Android apps | JVM footprint; JNI native code is outside the safety boundary |
| C# | Garbage collected (.NET) | Enterprise and Windows services, tooling | unsafe contexts and P/Invoke bypass checks |
| Swift | Automatic reference counting | Apple platform apps and services | Interop with C and Objective-C needs care |
| Python | Garbage collected, interpreted | Automation, data, glue code | Performance; native extension modules are often C |
- Replacing C or C++ in place: where no garbage collector is acceptable (kernels, firmware, real-time paths, libraries loaded into other processes), Rust is the usual candidate.
- New services: a managed language the team already knows usually gives most of the safety benefit at the lowest cost.
- Small teams: standardise on one memory-safe language per domain rather than several, so review skills and tooling stay concentrated.
- Large estates: set the language choice by component type in an architecture standard, and fund a central team to own interop libraries and build tooling.
The unsafe boundary
Every memory-safe language has escape hatches: Rust unsafe, C# unsafe and P/Invoke, Java JNI, Python C extensions, and any foreign function interface into C. Safety claims hold only for code that stays inside the safe subset, so the boundary has to be governed.
- What Rust unsafe allows: the Rust book lists five abilities: dereference a raw pointer, call an unsafe function or method, access or modify a mutable static, implement an unsafe trait, and access union fields. The borrow checker and other checks remain active inside
unsafeblocks. - Keep it small and wrapped: the book's advice is to keep
unsafeblocks small and wrap them in safe abstractions whose preconditions are checked, as the standard library does. - Review policy: require a written safety comment and a second reviewer with Rust experience for any new
unsafeblock; flag it in CI and report counts per crate. - Test unsafe code with Miri:
cargo miri testdetects out-of-bounds access, use-after-free, use of uninitialised data, misaligned access, invalid values and data races in the executions it runs. It cannot run most FFI or networking, and it shows the presence of undefined behaviour, not proof of soundness. - Treat the FFI boundary as an attack surface: validate lengths and ownership at every crossing; prefer generated bindings over hand-written declarations.
- Dependencies count: a memory-safe application can still link memory-unsafe C libraries (compression, image, TLS). The agencies' roadmap guidance asks for an explicit external dependency plan for C and C++ libraries, including open source.
Building a memory safe roadmap
The Case for Memory Safe Roadmaps (December 2023), issued by CISA, NSA, FBI, the Australian Signals Directorate's ACSC, the Canadian Centre for Cyber Security, the UK NCSC, New Zealand's NCSC and CERT NZ, asks manufacturers to publish a roadmap owned by a senior executive. Its recommended elements map directly to a programme plan.
| Roadmap element (agency guidance) | What to decide | Example evidence |
|---|---|---|
| Defined phases with dates and outcomes | Language evaluation, pilots, threat modelling, refactoring waves | Published plan with dated milestones |
| Date for new code in memory-safe languages | The cut-off after which new code is written only in a memory-safe language, and the exception process | Engineering standard, CI rule on new native files |
| Internal developer training | Who is trained, when, and how support is provided | Training records, internal guilds |
| External dependency plan | How C and C++ libraries, including open source, are replaced, sandboxed or hardened | Dependency inventory flagged by language |
| Transparency plan | How often progress is reported (for example quarterly or semi-annually) | Public or customer-facing updates |
| CVE support plan | Supplying CWE data for all CVEs in a timely way | CVE records with CWE fields completed |
For prioritisation, the guidance suggests starting with new and smaller projects, replacing self-contained components, and focusing first on security-critical code. In practice that means code that parses untrusted input (network protocols, file formats, media codecs, decompression), runs with high privilege, or sits on an internet-facing path. Use your threat model to rank components. See the Threat Modelling Guide.
How to migrate in practice
- Inventory native code: lines of C and C++ by component, owner, exposure to untrusted input and privilege, plus native dependencies. Your SBOM can flag components by language; see the SBOM Guide.
- Set the policy for new code: new components in a memory-safe language from a published date; new C or C++ files require an approved exception.
- Pilot one self-contained component: a parser or codec with a clean interface and good tests. Build the toolchain, CI, interop bindings and release process once, and measure performance and defect rates.
- Run old and new side by side: ship the new implementation behind a flag or selectable backend, compare outputs with differential testing and fuzzing, and keep the C or C++ version as the rollback path until the new one has run in production.
- Expand by priority: move high-exposure components in waves; for each wave, decide replace, isolate (separate process or sandbox) or harden.
- Retire the old code: remove the legacy implementation once the replacement has met its quality bar, so you do not maintain two attack surfaces.
- Report progress: publish roadmap updates on the cadence you committed to.
The Android experience shows why step 2 comes early: because vulnerabilities concentrate in new code, stopping the inflow of new memory-unsafe code delivers measurable results before large rewrites start.
Hardening the C and C++ that remains
NSA and CISA note that where memory-safe languages are not feasible, non-memory-safe code can still be made safer. These measures reduce risk; they do not make C or C++ memory safe.
| Measure | Specific setting or tool | Effect |
|---|---|---|
| Standard library hardening (libc++) | -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST | Bounds and valid-range checks on containers and iterators; recommended for production; no ABI change |
| Standard library hardening (libstdc++) | -D_GLIBCXX_ASSERTIONS | Precondition checks on library calls |
| Fortify | -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 with -O2 | Compile and run-time checks on unsafe libc calls |
| Stack protection | -fstack-protector-strong, -fstack-clash-protection | Canaries and stack guard probing |
| Control-flow protection | -fcf-protection=full (x86_64), -mbranch-protection=standard (AArch64) | Hardware-assisted defence against ROP and JOP |
| Initialisation | -ftrivial-auto-var-init=zero | Removes uninitialised stack variable leaks |
| Linker hardening | -Wl,-z,relro -Wl,-z,now, -Wl,-z,noexecstack, -fPIE -pie | Read-only relocations, non-executable stack, ASLR |
| Hardware tagging | Arm Memory Tagging Extension (MTE); CHERI (research, Morello prototype) | Detects or blocks many spatial and temporal errors at hardware level |
- Testing: run AddressSanitizer and UndefinedBehaviorSanitizer builds in CI and continuous fuzzing on every parser. See the Fuzzing Guide.
- Allocators: hardened allocators change exploitability. Google reports that a 2025 linear buffer overflow in Android's Rust CrabbyAVIF code (CVE-2025-48530) was caught before release, and that the Scudo hardened allocator's guard pages made it non-exploitable.
- Isolation: Chromium's Rule of 2 approach of not combining untrusted input, an unsafe language and high privilege in one process is a useful design rule where code cannot yet be migrated.
The full baseline is in the OpenSSF Compiler Options Hardening Guide for C and C++; check flag support for your compiler versions before enforcing.
Where identity fits
Out-of-bounds reads matter to identity teams because process memory holds session tokens, API keys and passwords. Leaked memory turns a parsing bug into credential theft, and the stolen tokens remain valid until revoked. Plan for this in incident response: when a memory disclosure flaw is patched, rotate secrets and invalidate sessions that the vulnerable process held. See the Secrets Management Guide and the Token and Session Security Guide.
Detection and measurement
- Monitor: new C and C++ files and new
unsafeblocks per merge, sanitiser and fuzzing crashes, and CVEs in your products and dependencies tagged with memory safety CWEs. - Metric: share of new code in memory-safe languages per quarter, by product (target rising toward 100 percent after the cut-off date).
- Metric: memory safety share of vulnerabilities as a percentage of all security fixes, using CWE data (the Android trend is the benchmark shape).
- Metric: exposed memory-unsafe surface lines of C and C++ that handle untrusted input or run privileged, tracked downward.
- Metric: unsafe density count of
unsafeblocks or FFI functions per thousand lines in memory-safe code, with review coverage at 100 percent. - Metric: hardening coverage share of native build targets compiled with the agreed flag baseline and standard library hardening mode.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SA-8 Security and Privacy Engineering Principles; SA-15 Development Process, Standards, and Tools | Language choice and toolchain standards as engineering controls |
| NIST SP 800-53 Rev. 5 | SA-11 Developer Testing and Evaluation, SA-11(1) Static Code Analysis, SA-11(8) Dynamic Code Analysis | Sanitisers, fuzzing, Miri and static analysis |
| NIST SP 800-53 Rev. 5 | SI-16 Memory Protection | Runtime protections such as non-executable memory and ASLR |
| NIST CSF 2.0 | PR.PS-06 | Secure software development practices integrated and monitored |
| ISO/IEC 27001:2022 Annex A | A.8.25 Secure development life cycle; A.8.28 Secure coding | Language and coding standards |
| CIS Controls v8.1 | Control 16 Application Software Security | Secure development process and code-level safeguards |
| MITRE ATT&CK | T1190 Exploit Public-Facing Application; T1203 Exploitation for Client Execution; T1068 Exploitation for Privilege Escalation | Techniques that memory corruption bugs most often enable |
Common failure patterns
- Rewrite everything first: large rewrites stall and leave new C and C++ flowing in meanwhile. Stop the inflow with a new-code policy before planning rewrites.
- Unreviewed unsafe and FFI: a Rust component full of raw pointers behind a thin wrapper inherits C's risks without C's review attention.
- Forgetting native dependencies: a memory-safe service still parses data with a C library. MongoBleed shows the cost of length handling errors in native parsing code: in the MongoBleed (CVE-2025-14847) disclosure, the zlib message decompression path reported the allocated buffer size rather than the decompressed length, returning uninitialised heap memory, which could include credentials, to unauthenticated clients.
- Treating over-reads as low severity: "only an information leak" is wrong when memory holds sessions. The CitrixBleed 2 (CVE-2025-5777) memory overread let unauthenticated attackers read leftover memory that researchers reported could contain user session tokens.
- Hardening flags in the wrong builds: flags and hardening modes enabled for debug builds only, or overridden by a vendor default of no hardening.
- No measurement: no CWE data on fixed vulnerabilities, so nobody can show whether the roadmap is working.
Practitioner checklist
- Inventory C and C++ by component, exposure to untrusted input, privilege and owner, including native dependencies.
- Publish a date after which new code is written in a memory-safe language, with an exception process.
- Pick one self-contained, high-exposure component as a pilot and build the toolchain and interop once.
- Ship replacements behind a flag with differential testing and fuzzing, keeping the old path for rollback.
- Require safety comments, second review and CI reporting for every
unsafeblock and FFI binding; run Miri on unsafe code. - Enforce a compiler hardening baseline and standard library hardening mode on all remaining native builds.
- Run sanitiser builds and continuous fuzzing on every parser of untrusted input.
- Record CWE data for every fixed vulnerability and track the memory safety share over time.
- Rotate secrets and invalidate sessions after any memory disclosure flaw in a process that held them.
- Report roadmap progress to leadership and customers on a fixed cadence.
Standards and references
- CISA, NSA and partners: The Case for Memory Safe Roadmaps
- CISA and NSA: Memory Safe Languages: Reducing Vulnerabilities in Modern Software Development
- MITRE: 2025 CWE Top 25 Most Dangerous Software Weaknesses
- Google: Rust in Android: move fast and fix things
- Chromium: Memory Safety
- The Rust Programming Language: Unsafe Rust
- OpenSSF: Compiler Options Hardening Guide for C and C++
- LLVM: libc++ Hardening Modes
Related NHI Mgmt Group resources: Secure Coding Guide · CWE Top 25 Guide · Fuzzing Guide · Software Supply Chain Security Guide · Open Source Licence Compliance Guide