Skip to content

Lesson 5 of 9 · 7 min read

Memory Corruption and the Mitigations Against It

Free lessonReviewed By the AISecurityInterviews editorial team
Reading position
In this lesson

TL;DR: Memory bugs break either bounds (out-of-bounds write or read) or lifetime (use-after-free, double free). They become exploits when the corrupted memory holds control data (return addresses, function pointers) or decisions (lengths, permission flags). Each mitigation blocks one step: canaries detect stack smashing, NX stops running injected bytes so attackers reuse existing code, ASLR hides where that code is until a leak reveals it, and CFI and shadow stacks restrict where indirect jumps and returns may land. None fixes the bug. Memory-safe languages remove the classes.

Before you read

Pick an answer before you read further. The lesson below explains it, and guessing first, right or wrong, helps the explanation stay with you.

  1. 1A bug lets an attacker read 64 KB of memory past the end of a buffer, but never write. Can it be critical?

The two families

FamilyBugWhat happens
SpatialOut-of-bounds write (buffer overflow)Overwrites neighbouring data or control information
SpatialOut-of-bounds read (over-read)Discloses neighbouring memory
SpatialInteger overflow in a size calculationAllocates too little, then writes too much
TemporalUse-after-freeA stale pointer reads or writes memory now owned by another object
TemporalDouble freeCorrupts the allocator's bookkeeping
TemporalUninitialised readLeaks old data or uses garbage as a pointer
TypeType confusionInterprets memory as the wrong type, so an integer becomes a pointer

Precision matters in interviews: a buffer overflow is a write; an over-read is a different bug with different consequences.

From a bug to control

An exploit needs a primitive (what the bug lets you do: write these bytes there, read from here) and a target (what to corrupt):

  • Return addresses on the stack: the classic target.
  • Function pointers and C++ vtable pointers: redirect an indirect call.
  • Allocator metadata: turn a heap overflow into a write somewhere else.
  • Lengths and bounds: turn a small write into a large read or write.
  • Security decisions: an is_admin flag, a credentials structure. Changing them redirects nothing and still wins (a data-only attack).

What each mitigation blocks

LESSON · MEMORY CORRUPTION AND THE MITIGATIONS AGAINST IT
A classic stack exploit, step by step

Protecting: Code execution in the process

  1. 1

    1 · Stack canary

    detects the smash before return

    • Stops: Linear overflow over the return address
  2. 2

    2 · NX / W^X

    data pages cannot execute

    • Stops: Jump to bytes the attacker wrote
  3. 3

    3 · ASLR + PIE

    addresses unknown without a leak

    • Stops: Chain existing code at known addresses
  4. 4

    4 · Shadow stack / CFI

    returns must match calls

    • Stops: Return into the chain
Each layer blocks one step, and each has a precondition that defeats it: a leaked canary, code reuse, a leaked pointer, a coarse policy. Together they turn a one-bug exploit into a multi-bug one.
  • Stack canary. A random value between local buffers and saved return data, checked before return. Defeated by leaking it, or by a write that skips over it.
  • NX (DEP, W^X). Memory is writable or executable, not both. Attackers answer with return-oriented programming: chaining short sequences of existing instructions that end in a return.
  • ASLR and PIE. Libraries, heap, stack and (with PIE) the executable load at random addresses. One leaked pointer reveals a whole module's layout because modules move as units.
  • RELRO. Makes the dynamic linker's global offset table read-only, removing an easy write target.
  • CFI and shadow stacks. Forward-edge checks (Windows CFG, Intel IBT, ARM BTI) restrict indirect calls to valid targets; backward-edge protection (Intel CET shadow stacks, ARM pointer authentication) makes overwritten return addresses fail.

The mitigations in numbers and versions

MitigationWhat it doesNumbers and availability
ASLRRandomises where code, stack, heap and libraries loadLinux x86-64 randomises the mmap base with 28 bits by default (tunable up to 32); randomize_va_space=2 also randomises the heap
Non-executable memory (DEP, NX)Data pages cannot run as codeBuilt into Windows since XP SP2 and Server 2003; Microsoft says it is not meant to stop every exploit on its own
Stack canariesA secret value before the return address, checked on returnGCC's -fstack-protector-strong protects functions with local arrays or address-taken locals
Forward-edge CFIIndirect calls may only go to valid targetsWindows Control Flow Guard (/guard:cf at compile and link), Clang CFI (needs link-time optimisation), Intel IBT, Arm BTI (ARMv8.5)
Backward-edge protectionReturns go back where they came fromIntel CET shadow stacks (Linux user space since 6.6, Windows per binary with /CETCOMPAT), GCC -fcf-protection=full
Arm pointer authenticationA cryptographic code in a pointer's unused high bitsARMv8.3; with 48-bit virtual addresses the code is 7 bits; per-process random keys on Linux
Arm memory tagging (MTE)A 4-bit tag on each 16-byte granule must match the pointer's tagARMv8.5; synchronous mode stops the faulting access, asynchronous mode reports later; Android apps opt in

Worked example: probabilistic mitigations and the restart budget

Several mitigations are probabilistic, and their strength depends on how many tries the attacker gets.

  • ASLR, 28 bits: about 268 million possible bases, so a blind guess almost never works. That is why real exploits use an information leak instead: one leaked pointer reveals the whole module's position, because modules move as units, and the entropy for that module drops to zero.
  • Pointer authentication, 7 bits: a forged return pointer passes with probability 1 in 128, and each wrong guess crashes the process. Against a process that is not restarted, that is strong; against a server that silently restarts crashed workers, the attacker expects success in about 128 tries.
  • Memory tagging, 4 bits: a stale or overflowing pointer matches its target's tag by chance 1 time in 16. Synchronous mode, where the bad access never happens, is the one to use where accuracy matters.

The design lesson: pair probabilistic mitigations with crash monitoring and restart limits, so repeated crashes are treated as an attack signal rather than a reliability blip.

LESSON · MEMORY CORRUPTION AND MITIGATIONS
Where each mitigation sits on the path from bug to control

Gates, with their strength

MTE, canaries

  • 4-bit tags catch most bad accesses

ASLR

  • 28 bits, gone after one leak

CFI, shadow stacks, PAC

  • Valid targets only; 7-bit PAC

The way around the gates

Data-only attack

  • Flip a flag or credential pointer
  • No control-flow change at all
1/3

Catch the bug itself

Memory tagging and canaries detect the corrupting access, probabilistically.

What mitigations do not do

They do not remove the bug, prevent over-reads, stop data-only attacks, or protect code built without them (third-party libraries, JIT output). So a triage note saying "mitigations are enabled, so low severity" is wrong: severity depends on the primitive, who can reach the bug and what the process can access. Mitigations change likelihood and cost, not impact.

The fix for the class

Memory-safe languages enforce bounds and lifetimes by construction (Rust's ownership and borrow checking, bounds-checked indexing; garbage-collected languages). Google has reported that shifting new Android development to memory-safe languages sharply reduced the share of memory-safety vulnerabilities, even though most existing C and C++ remained, because bugs cluster in new and recently changed code. The realistic programme: memory-safe languages for new parsers and network-facing code, sandboxing for the legacy parsers that remain, hardened builds, and continuous fuzzing with sanitizers.

The evidence for memory-safe languages

The numbers are consistent across large codebases. Microsoft reported in 2019 that about 70% of the CVEs it assigns each year were memory-safety issues; the Chromium project reported about 70% of its high-severity security bugs were memory-safety bugs. Android reported in 2024 that the share of memory-safety vulnerabilities fell from 76% in 2019 to 24%, as new code moved to memory-safe languages, while most existing C and C++ stayed in place; Google described the relationship as a correlation driven by new code. CISA, the NSA, the FBI and allied agencies published "The Case for Memory Safe Roadmaps" in December 2023, urging vendors to publish plans, and the NSA and CISA followed with guidance on memory-safe languages in June 2025. The practical shape of a migration: new code and especially parsers in memory-safe languages, legacy parsers isolated in sandboxed processes, hardened builds for what remains, continuous fuzzing with sanitizers, and tracking the share of memory-safety vulnerabilities over time.

Worked example: triaging a fuzzer crash

AddressSanitizer reports a heap-buffer-overflow WRITE of 16 bytes in an image decoder used by a messaging service. Reachability: remote, no interaction (images arrive in messages). Primitive: attacker-controlled bytes past a heap buffer whose size the attacker influences. Process: the media service, which also holds message keys. Mitigations: ASLR, NX, CFI on. Verdict: treat as exploitable remote code execution in a sensitive process; fix immediately, look for the same size calculation elsewhere, add the input as a regression test, and move decoding into a sandboxed process with no keys.

Misconceptions interviewers listen for

  • "Return-oriented programming gives root." It gives the privileges of the vulnerable process.
  • "Mitigations are enabled, so the bug is low severity." Mitigations change exploit cost, not the presence of a bug; one leak defeats ASLR.
  • "Canaries stop all stack bugs." Not writes that skip the canary, leaked canaries or overwrites of locals before it.
  • "CFI covers returns." Forward-edge and backward-edge protections are separate mechanisms.
  • "Rewrite everything in Rust." The evidence is strongest for new code; legacy code is isolated and hardened.

What if · interview follow-ups

Hold your answer as the constraints change

You are the security engineer for a company that ships a C++ media-processing service that parses uploaded files, running on Linux x86-64 and Arm servers. The interviewer asks how far each mitigation goes.

  1. Start here

    What if an attacker has found a heap overflow but has no information leak?

How this shows up in interviews

"Explain ASLR, DEP and stack canaries", "what is ROP?", "is this crash exploitable?" and "how would you reduce memory-safety risk in a large C++ codebase?" all draw on this page. Interviewers listen for the step each mitigation blocks, the precondition that defeats it, and the refusal to treat mitigations as a fix.

Say it in 30 seconds: "Memory bugs break bounds or lifetimes, and become exploits when they corrupt control data or decisions. Canaries catch stack smashing, NX forces code reuse, ASLR hides addresses until something leaks, CFI and shadow stacks restrict where control can go. They raise the cost; they don't remove the bug, and data-only attacks walk around the control-flow ones. The real fix is memory-safe code for anything parsing untrusted input, with sandboxing for what's left."

Check yourself

Say or write your answer first, then reveal. Explaining it from memory, in your own words, is the skill an interviewer is checking.

  1. 1Why does one leaked library address defeat ASLR for that library?

  2. 2Which mitigation stops an attacker from overwriting a 'role' field in a user object from 'user' to 'admin'?

  3. 3Why does one information leak usually defeat ASLR for a module?

  4. 4What is a data-only attack, and which mitigations does it bypass?

Practise it as an interviewer asks it

Related lessons