Microsoft reported that around seventy percent of the security vulnerabilities it patches are memory safety issues. The Chromium project has published a similar figure for its own codebase.
Two decades of mitigations, static analysis, and code review have not moved that number much. The reason is structural, and it is worth understanding before reaching for a tool.
This covers what the bug classes actually are at the memory level, how coverage-guided fuzzing finds them, and which sanitizer catches which defect.
Where this sits relative to the platform: fuzzing is not a CodeAnt AI capability, and this article does not claim otherwise. What CodeAnt covers is the source-level side, where AI code review and SAST catch a share of these classes before compilation, at the point they are cheapest to fix. The rest of this guide is tool-agnostic.
What is a memory safety vulnerability?
A memory safety vulnerability is a defect that allows a program to read or write memory it was not intended to access.
The class divides cleanly in two, and the distinction determines how you find each half.
Spatial safety concerns boundaries. A spatial violation accesses memory outside the bounds of the object it was pointed at. Buffer overflows and out-of-bounds reads live here.
Temporal safety concerns lifetime. A temporal violation accesses memory that is no longer valid for the object in question. Use-after-free and double-free live here.
Why C and C++ carry the burden
Neither language checks bounds on array access, and neither tracks object lifetime automatically.
Both decisions were deliberate performance trade-offs that were entirely reasonable when the languages were designed.
The consequence is that in C and C++, memory safety is a property the programmer must maintain by hand across every access, in every function, forever. Any single lapse is a potential vulnerability.
Managed languages check bounds and manage lifetime at runtime, which eliminates most of the class at a cost in performance and control.
Rust eliminates most of it at compile time instead, which is the reason for the current interest in it.
Buffer overflow, explained with the stack layout
A buffer overflow occurs when a program writes more data into a buffer than the buffer can hold, and the excess overwrites adjacent memory.
The classic case is the stack, and the memory layout is what makes it exploitable.
The stack frame
When a function is called, the processor pushes a return address onto the stack, and the function then allocates space for its local variables.
On a typical architecture the layout looks like this, with the stack growing downward.
The buffer is below the return address, and a write that runs past the end of the buffer moves upward toward it.
If input is longer than 64 bytes, strcpy keeps writing. Past the buffer it hits the canary, then the saved frame pointer, then the return address.
Overwrite the return address and you control where execution resumes when the function returns. That is the entire mechanism.
Heap overflow differs in what it corrupts
A heap overflow overruns a heap-allocated buffer instead. There is no return address adjacent to it, so the target is different.
What sits next to a heap allocation is allocator metadata, chunk headers describing size and free status, and other allocations.
Corrupting that metadata can turn a subsequent malloc or free into an arbitrary write.
Heap exploitation is more allocator-dependent and generally harder, which is one reason attention has shifted there as stack protections improved.
Off-by-one and integer issues
Two adjacent classes produce the same outcome through different means.
An off-by-one writes exactly one byte past the buffer, often through a loop condition using <= instead of <. A single byte is frequently enough.
An integer overflow in a size calculation produces an undersized allocation. The subsequent write is correctly bounded against the number the code thinks it computed, and that number is wrong.
The write loop here contains no bug. The bug is upstream in arithmetic, which is why this class survives review so reliably.
Use-after-free and why it is harder to exploit
A use-after-free occurs when a program accesses memory through a pointer after that memory has been freed.
The danger is the reallocation window. Between the free and the use, the allocator may hand that memory to a different allocation.
If an attacker can influence what gets allocated in that window, they control the contents of the object the program is still using.
When the reallocated object has a different type, the result is type confusion. A field the original object treated as a function pointer now contains attacker-chosen data.
Two related defects share the mechanism. A double free frees the same pointer twice, corrupting allocator state. A dangling pointer is the general case of a pointer outliving what it referenced.
Use-after-free is harder to exploit than a stack overflow because it requires allocator grooming, meaning shaping the heap so the right thing lands in the freed slot.
It is also harder to find by inspection, because the free and the use are frequently in different functions and different files.
What is fuzzing?
Fuzzing is automated testing that feeds a program large volumes of generated input and watches for crashes, hangs, or sanitizer-detected violations.
The premise is simple. A memory safety bug is triggered by an input the developer did not anticipate. Generating enormous numbers of unanticipated inputs finds them.
Why naive random input fails
Purely random bytes almost never reach interesting code. A parser rejects malformed input in its first few checks, and the deep code where bugs live is never executed.
If a program requires a specific four-byte magic header, random generation reaches the parser body once in roughly four billion attempts.
How coverage guidance changes it
Coverage-guided fuzzing solves this with a feedback loop. The binary is compiled with instrumentation that records which code paths execute for a given input.
The loop is short. Take an input from the corpus. Mutate it, by flipping bits, splicing, inserting known-interesting values. Run it and record coverage. If it reached new code, add it to the corpus. Repeat.
The corpus becomes a growing collection of inputs that collectively explore more of the program. The fuzzer effectively learns the input format by observing which mutations get further in.
That single change is what took fuzzing from a curiosity to the most productive bug-finding technique available for compiled code.
Seeds, corpus, and minimization
Three practical concepts do most of the work.
Seeds are the starting inputs. Good seeds are small, valid examples of the input format, and they dramatically shorten the time to first finding.
The corpus is the accumulated set of coverage-increasing inputs. It is an asset. Preserve it between runs, because rebuilding it costs hours.
Minimization reduces both a crashing input and the corpus itself to the smallest form producing the same behaviour. A two-hundred-byte crash reproducer is far more useful to a developer than a two-megabyte one.
Fuzzing tools compared
Tool | Type | Best for |
|---|---|---|
AFL++ | Coverage-guided, out of process | General-purpose, actively maintained, strong mutation engine |
libFuzzer | Coverage-guided, in process | Library and API fuzzing, integrates with LLVM sanitizers |
honggfuzz | Coverage-guided | Persistent fuzzing, good hardware counter support |
OSS-Fuzz | Managed service | Open-source projects, continuous fuzzing at no cost |
syzkaller | Coverage-guided | Operating system kernels and system call interfaces |
Jazzer | Coverage-guided | JVM languages |
cargo-fuzz | Wrapper for libFuzzer | Rust, particularly |
For most C and C++ work the choice is between AFL++ and libFuzzer, and it comes down to process model.
libFuzzer runs in process, which makes it very fast and means a crash takes down the fuzzer.
AFL++ runs the target out of process, which is more robust and handles targets you cannot easily restructure into a harness.
If your project is open source, OSS-Fuzz is the highest-leverage option available, because it runs continuously on Google infrastructure and reports findings to maintainers.
How to write a fuzz harness
A harness is the adapter between fuzzer-generated bytes and your API. The standard libFuzzer entry point is four lines.
Four rules make the difference between a harness that finds bugs and one that burns CPU.
Target a single entry point. A harness covering one parser finds bugs. A harness invoking your whole application spends its time in setup code.
Be deterministic. The same input must produce the same execution. Randomness, time dependence, and network calls break the coverage feedback loop.
Be fast. Throughput is executions per second, and it multiplies directly into bugs found. Move initialisation outside the fuzz entry point.
Do not over-validate. Rejecting malformed input at the top of the harness defeats the purpose. Reject only what would crash for uninteresting reasons.
Which sanitizer catches what
A fuzzer detects a crash. Many memory safety violations do not crash immediately, and some never crash at all while still being exploitable.
Sanitizers close that gap by instrumenting the binary to detect the violation at the moment it occurs.
Sanitizer | Detects | Cost |
|---|---|---|
AddressSanitizer | Heap and stack overflow, use-after-free, double free, use-after-return | ~2x slowdown, ~3x memory |
MemorySanitizer | Reads of uninitialised memory | ~3x slowdown |
UndefinedBehaviorSanitizer | Integer overflow, misaligned access, invalid casts | Low, configurable |
ThreadSanitizer | Data races | ~5x to 15x slowdown |
AddressSanitizer is the default pairing for fuzzing, because it catches the bulk of the class at an acceptable cost.
UndefinedBehaviorSanitizer is worth enabling alongside it, and it catches the integer overflow class described earlier.
MemorySanitizer and ThreadSanitizer are not compatible with AddressSanitizer in the same build, so run them as separate campaigns.
The critical point. Fuzzing without sanitizers finds only the bugs that happen to crash, which is a small fraction of the total. Fuzzing with AddressSanitizer finds the violations themselves.
Why mitigations decide whether a bug is exploitable
Finding a memory safety bug and determining its severity are separate exercises, and the second depends on how the target was compiled.
Stack canaries, non-executable data pages, address space layout randomisation, position-independent executables, and control flow integrity each raise the cost of turning a memory error into code execution.
A stack overflow in a binary compiled with canaries, non-executable pages, full ASLR, and control flow integrity is a substantially different finding from the same bug in a binary with none of those, even though the underlying code defect is identical.
Check the mitigations before writing the severity assessment, not after.
Where memory-safe languages change the picture
The strategic answer to a class of bug is to eliminate the conditions that produce it.
Rust enforces both spatial and temporal safety at compile time.
The ownership model tracks which code owns a value, the borrow checker verifies that references never outlive what they point to, and bounds are checked on indexing.
Managed languages including Go, Java, and C# achieve much of the same through runtime checks and garbage collection, at a different cost profile.
United States government agencies including CISA and the NSA have published guidance recommending adoption of memory-safe languages for new development.
And the Office of the National Cyber Director issued a report to the same effect.
Where memory safety guarantees stop
Three limits are worth stating, because overclaiming here is common.
Unsafe blocks. Rust's unsafe suspends the guarantees, and it is required for certain low-level operations. Bugs concentrate there, which is exactly why cargo-fuzz exists.
Foreign function interfaces. Calling into a C library from a memory-safe language inherits that library's bugs. The boundary is not a firewall.
Logic vulnerabilities. Memory safety eliminates one class. Authorization bypass, injection, insecure deserialisation, and business logic flaws are entirely unaffected, and they are the majority of what an application penetration test finds. IDOR is a good example: it is one of the most common findings in real audits, and it has nothing to do with memory layout at all.
A memory-safe rewrite removes roughly seventy percent of one company's CVE class. It removes zero percent of the rest.
Where CodeAnt AI fits, and where it does not
Direct statement first. CodeAnt does not run fuzzing campaigns. If you need a fuzzing programme, AFL++ and OSS-Fuzz are the answer and no platform substitutes for them.
Two adjacent things are true and worth stating precisely.
Source-level detection catches part of the class earlier. AI code review and SAST analyse code as written, which is where an unbounded copy, a missing bounds check, or an unchecked size calculation is cheapest to fix.

Static analysis will not find every memory safety bug, and any tool claiming otherwise is overselling.
It reliably finds the obvious cases, like the strcpy call earlier in this article with no length check anywhere near it, and, more usefully, it finds them before the code is compiled and shipped.
Whitebox flow analysis targets the adjacent classes. The AI penetration testing pipeline traces tainted input from request handlers into query construction, command execution, and authentication decisions, and follows user-controlled URLs and paths into HTTP clients and filesystem reads.
Those are the injection and authorization classes rather than the memory safety class. They are also the majority of what actually gets exploited in web-facing applications, which is the same point made in CodeAnt's IDOR guide.
On the value of examining widely used code carefully, CodeAnt's research team has disclosed 100+ CVEs, including CVE-2026-29000, a CVSS 10.0 authentication bypass in pac4j-jwt that sat in reviewed, widely deployed code for years, and CVE-2026-28292, a CVSS 9.8 remote code execution bug in simple-git.
Both are logic and authorization defects rather than memory safety bugs, which is worth noting given this article's subject: the highest-severity findings in modern, mostly-managed-language software increasingly are not the class this guide covers, even though that class still causes roughly seventy percent of what Microsoft and Chromium individually patch in their C and C++ surfaces.
Those two findings sit in packages that together represent a share of the 1.85B+ monthly downloads CodeAnt's research has audited.
The memory safety checklist
Build configuration
Enable AddressSanitizer and UndefinedBehaviorSanitizer in a dedicated test build.
Verify mitigations are on in release builds. Canaries, non-executable pages, position-independent executables, full RELRO.
Enable compiler bounds-check hardening where your toolchain supports it.
Fuzzing programme
Start with one parser. The component that handles untrusted input first is the highest-value target.
Provide small valid seeds. Time to first finding drops sharply with good seeds.
Persist the corpus between runs. It is an accumulated asset, not a temporary file.
Run continuously, not as a one-off. Fuzzing finds more the longer it runs.
Enrol open-source projects in OSS-Fuzz, which is free and continuous.
Triage
Minimize every crash before filing it.
Deduplicate by stack trace, since one bug commonly produces many crashing inputs.
Check mitigations before assigning severity.
Strategic
Use memory-safe languages for new components where performance requirements allow.
Fuzz
unsafeblocks and FFI boundaries specifically in otherwise memory-safe codebases.Do not assume memory safety covers logic flaws. It covers none of them.
Where this leaves you
Memory safety bugs persist because C and C++ make correctness a per-access manual obligation, and humans do not sustain that indefinitely across large codebases.
The practical response has three layers. Compile with sanitizers and mitigations, fuzz the components that parse untrusted input, and write new code in memory-safe languages where the performance budget allows.
None of that touches the logic vulnerabilities that make up most of what an application penetration test actually finds. Memory safety is one class, comprehensively addressable, and not the whole problem.
Related reading on CodeAnt AI: the IDOR guide for the logic-vulnerability class this article's mitigations do nothing for, and CVE and NVD Data Explained for how a memory safety bug like the pac4j-jwt finding above actually becomes a scored, trackable record once it is found.


