NewApp Monitoring is liveSign up free
AgentbeamBeta
Course outline

Learn / Level 4 / lesson 3

Use-after-free

What happens when a program keeps using memory after giving it back. A coat-check analogy, a tiny C example, the real Office bug CVE-2017-0261, and the fixes that work.

8 min read · Yash Thakker · updated 2026-10-24

use-after-free · dangling pointer · CWE-416 · memory safety

A ticket still pointing at a box that has been returned and handed to someone else

What you will learn

  • Explain a dangling pointer and why reusing freed memory is unsafe
  • Walk through the three steps of a use-after-free bug
  • Reproduce and detect one safely with a sanitizer
  • Name the main defences: clearing pointers, ownership rules and memory-safe languages

Before this: Buffer overflows

A buffer overflow is writing outside a box. The second big family of memory bugs is subtler: using a box you have already handed back. Nothing about the bytes is wrong. The mistake is in time, not in space. A is the code asking "what is in this box?" after the box has been given to someone else.

The coat-check analogy

Think of the heap as a coat-check room.

  1. You hand over your coat and get a numbered ticket. The ticket is the pointer; the coat is the data.
  2. When you are done, you return the ticket and the room reclaims the hook. That is free.
  3. The room reuses the hook for the next customer's coat.
  4. You find an old ticket in your pocket, walk back, and ask for coat 17.

You receive a stranger's coat. If you hang something on the hook, you have damaged a stranger's property. The room does not stop you: the ticket still has a number, and numbers are not cancelled when the hook is freed. That leftover ticket is called a .

The three steps in code

#include <stdlib.h>
#include <string.h>

int main(void) {
    char *note = malloc(16);       /* 1. get a block, hold a pointer to it */
    strcpy(note, "hello");
    free(note);                    /* 2. give the block back (note still holds the old address) */
    note[0] = 'X';                 /* 3. use it anyway. This is the bug. */
    return 0;
}

Three steps: allocate and hold a pointer, free the block, the block is reused by new data while the old pointer still reads it

On many systems this runs without any complaint. The write goes to a block that is free, so nothing visibly breaks. That quietness is the trap. Bugs like this survive testing for years because they only bite when something else has taken over that block between step 2 and step 3.

Two owners of one box: the old pointer still reads the block, but the new owner has filled it with different data

Why it is more than a crash

Memory allocators recycle blocks quickly. Free a block and the very next request of the same size will often receive that very block. Now two parts of the program think they own the same box:

  • The old code still has its dangling pointer and believes the box holds, say, an object of type A.
  • The new owner has stored something different there, perhaps data that came from outside the program, such as a file or network message.

When the old code reads through its pointer, it reads whatever the new owner put there and trusts it as an A. If an A contains, for example, a pointer to a function to call next, the program may now jump where that foreign data says. When the old code writes, it corrupts the new owner's data. Either way, whoever controls what lands in the recycled block has some control over the program's behaviour. That is the reason a bug that looks like an accounting mistake is treated as a route to running code.

Where these bugs come from

Almost always from unclear ownership. Some part of the program frees an object while another part still holds a reference, usually in code with many moving pieces: a browser handling a web page while scripts delete the elements it is drawing, a document editor handling an embedded object, or a network server tearing down a connection while a reply is still in flight. The dangerous pattern is two owners, one of whom thinks they are finished.

Prevention and detection

  • Clear the pointer after freeing. Setting note = NULL; right after free(note); means a later mistaken use is a clean crash, not silent corruption. It is a small habit with big value, but it does not help when a second pointer to the same block exists.
  • One owner per object. Languages and libraries that make ownership explicit (smart pointers in C++, ownership rules in Rust) turn this class of bug into a compile-time error.
  • Memory-safe languages. Python, Java, Go and similar languages free memory only when nothing refers to it any more, so a stale pointer cannot exist. remove the whole bug class.
  • Sanitizers and fuzzing. AddressSanitizer notices every access to freed memory during testing. Fuzzers feed a program millions of odd inputs to trigger rare orderings.
  • Quarantine allocators and hardening. Some systems delay reuse of freed blocks so that a stale pointer hits an unused hook for longer. This is mitigation, not a cure.

A real case

What the record tells us, and what we can reason from it:

  • Class and delivery. CWE-416 is the use-after-free weakness. The CVSS vector marks user interaction as required, so as with the earlier Office bug, a person has to open crafted content.
  • Impact. The record calls it a remote code execution vulnerability. The code would run with the permissions of the user who opened the document.
  • In the wild. It appears in the CISA Known Exploited Vulnerabilities catalogue, added 2022-03-03.
  • Fix. Apply the Microsoft updates referenced in the record.

Notice what the record does not say: the exact objects involved. Detailed internals are usually not published at the time of a patch, and we should not guess. For learning purposes the general mechanism is enough: an object was released while something still used it.

How AI changes this

Attackers. Use-after-free bugs depend on sequences, so they were historically found with fuzzers and expert code reading. AI models can help reason about object lifetimes across many files and can speed up the step from "this crashes" to "this is exploitable". Wiz reports AI models did much of the discovery and exploit development work on PHP2Shell, a set of memory flaws in PHP; our post on how PHP2Shell works explains the stale-state mechanism, which is in the same family of lifetime mistakes. Treat the speed as new, the bug class as old.

Defenders. Pair AI review with sanitizers. A model can point at a suspicious free; a sanitizer can prove an access happened. Trail of Bits' regression-hunt plugin is an example of requiring a live proof for each claim, which is the right instinct for any AI-assisted bug report.

Agents. Agents that generate C or C++ may produce freed-pointer mistakes just like humans, and are often asked to fix leaks, where over-eager free calls create the opposite bug. Ask for tests that run under a sanitizer before accepting changes to memory code.

Try it

This is a demonstration on your own machine; it only crashes and reports. Save as uaf.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *note = malloc(16);
    strcpy(note, "hello");
    free(note);
    printf("after free: %s\n", note);   /* reads freed memory */
    return 0;
}
cc -fsanitize=address -g uaf.c -o uaf
./uaf

AddressSanitizer stops the program with heap-use-after-free. The report lists three stack traces: where the bad read happened, where the block was freed, and where it was allocated. Reading those three together is exactly how engineers diagnose these bugs. Now add note = NULL; after the free and rebuild: the program still fails, but the failure is a plain null dereference at the line you wrote, which is easier to spot.

Check yourself

In the coat-check analogy, what is the dangling pointer and why does the room not stop you using it?

It is the old ticket. The ticket is just a number; freeing the hook does not cancel every copy of the number. The computer will follow the address without checking whether it is still yours.

Why can a use-after-free let someone influence the program even though they never touch the buggy code directly?

Because the allocator often reuses the freed block for the next similar request. If the next owner's contents come from outside, such as a file or a message, the old code ends up reading or writing data the outsider shaped.

Setting a pointer to NULL after free is good practice. Name a situation where it still does not protect you.

When a second pointer to the same block exists elsewhere. Clearing one copy leaves the other dangling, which is why clear ownership of each object matters more than any single habit.

Next

We have seen writes that go too far and uses that come too late. Next is the third kind: reading too much, which leaked secrets from a large part of the internet.