CWE-825 is the dereference of a pointer that once held a valid memory location but no longer does. The memory was released. The pointer was not forgotten. You may know it as a dangling or stale pointer.
MITRE lists C and C++. It matters wherever manual memory management meets long lived pointers: browsers, parsers, network services, VPN gateways.
How it happens
When a product frees memory but keeps a pointer to it, that memory can be allocated again for something else. Reading or writing through the old pointer touches data now owned by a different function or process.
// Vulnerable
struct conn *c = get_conn(id);
free(c);
log_close(c->peer); // c is expired, memory may be reused
Real examples from the record:
- CVE-2008-5013: access of an expired memory address leads to arbitrary code execution.
- CVE-2010-3257: a stale pointer issue leads to denial of service and possibly other consequences.
- CVE-2023-26463: a chain in an IPSec VPN product where one variable was used for multiple purposes in the same function, leading to incorrect access control and an expired pointer dereference.
- CVE-2008-0062: a chain where an unknown message type may cause a reference to uninitialized memory, resulting in a null pointer dereference or dangling pointer, possibly crashing the system.
Related but distinct is CWE-826, where the resource is released while it is still meant to be in use.
What an attacker gains
MITRE lists:
- Control of data read. If the expired pointer is read, an attacker may be able to control the data the application ingests.
- Crash. If the location is inaccessible, malformed or NULL, the program may terminate.
- Code execution. If the pointer is used for a function call or points to unexpected data in a write, code execution may be possible.
How to find it
MITRE names automated static analysis, which can follow data flow from free to later use, and dynamic analysis with runtime checks such as AddressSanitizer. Stale pointer bugs often hide on rare paths, such as error handling and teardown, so fuzzing and failure injection are worth the effort.
For vulnerable libraries in your dependencies, the free in-browser dependency scan needs no login, keeps your file on your machine, and covers up to 100 dependencies. Reachability analysis then shows whether your code calls the vulnerable function.
How to fix it
MITRE gives two mitigations.
- Choose a language with automatic memory management at design time.
- Set pointers to NULL after freeing them. MITRE adds that complex data structures with multiple pointers to the same object can lower the usefulness of this strategy.
Further practices that follow from the cause:
- Give every heap object one clear owner responsible for freeing it.
- In C++, use unique_ptr and shared_ptr instead of raw owning pointers.
- Use a helper that frees and clears in one step.
#define FREE_AND_NULL(p) do { free(p); (p) = NULL; } while (0)
struct conn *c = get_conn(id);
log_close(c->peer);
FREE_AND_NULL(c);
- Run tests under AddressSanitizer in CI.
Try it yourself
Pick a library that parses untrusted input in your own project and list who owns each object it allocates. Where two components hold a pointer to the same object, note which one frees it. Every place the answer is unclear is a candidate for this bug.
Quick check
How does CWE-825 differ from CWE-826?
In CWE-825 the release was correct, but a pointer to the freed memory is used later. In CWE-826 the release itself was wrong, because the resource was still meant to be valid.
Does setting the pointer to NULL after free fix everything?
No. It clears that one pointer, but copies of the pointer elsewhere stay stale. Clear ownership rules are the stronger fix.