CWE-476, NULL Pointer Dereference, happens when code uses a pointer or reference that it expects to be valid but is NULL. MITRE rates the entry Stable, and lists C, C++, Java, C# and Go.
For a network service, a bug that lets one request kill the process is a real availability problem, even when it never leads to code execution. It is also one of the most common crash causes in real code.
How it happens
Something returns NULL to signal failure, and the caller does not check.
// Vulnerable: malloc and fopen can both return NULL
char *buf = malloc(len);
memcpy(buf, input, len); /* crashes if buf is NULL */
FILE *f = fopen(path, "r");
fgets(line, sizeof line, f); /* crashes if f is NULL */
The same thing happens in Java and C# as a NullPointerException or NullReferenceException, usually when a lookup returns nothing.
User u = users.get(id); // returns null when id is unknown
return u.getName(); // throws NullPointerException
The record gives a range of observed cases. CVE-2024-41130 is a C++ library for LLM inference with a NULL dereference when a read operation fails. CVE-2005-3274 is a race condition that corrupts a table if a timer fires while it is being changed, ending in a NULL dereference, with locking also involved. CVE-2002-1912 is a large number of packets leading to a NULL dereference. CVE-2005-0772 is a packet with an invalid error status value that triggers one.
What an attacker gains
MITRE lists two outcomes.
- Availability. A NULL dereference usually fails the process unless exception handling is available and in use. Even with exception handling, it can be very hard to return the software to a safe state.
- Integrity and confidentiality, in rare cases. When NULL equals the 0x0 address and privileged code can access it, reading or writing memory is possible, which may lead to code execution.
How to find it
MITRE lists several methods.
- Fuzzing, stress testing with odd inputs, and fault injection, which feed many diverse inputs and watch for crashes.
- Manual dynamic testing of unlikely error conditions: low memory, insufficient permissions, a transaction interrupted midway, connectivity disabled.
- Static analysis, which can model data flow to a dereference with no prior check.
- Compiler assisted runtime checks such as AddressSanitizer for C and C++, which MITRE rates as moderately effective and which needs crafted inputs to reach the faulty code.
How to fix it
MITRE's mitigations, in plain steps.
- Check any pointer that could be modified or come from a function that can return NULL before you use it.
- In multithreaded code, use proper locking around the check and the use. Checking a return value is usually enough alone, but a race (CWE-362) can still change the pointer between check and use.
- Check the result of every function that returns a value and confirm it is not NULL before acting on it.
- Where you can choose, select a language that is not susceptible to these issues.
- Validate every variable that receives data from external sources so it only holds expected values.
- Explicitly initialize variables and data stores at declaration or just before first use.
char *buf = malloc(len);
if (buf == NULL) {
return -1; /* fail cleanly */
}
memcpy(buf, input, len);
User u = users.get(id);
if (u == null) {
throw new NotFoundException(id);
}
return u.getName();
Try it yourself
See how a code scan flags a dereference that follows an unchecked call.
Click through it at your own pace, or open it full screen.
Quick check
If I check the pointer, am I safe in threaded code?
Not always. Another thread can change the pointer after your check. Hold a lock across the check and the use.
Why does fuzzing find these well?
Crashes are easy to detect. A fuzzer keeps changing inputs until one reaches an unchecked NULL, and the crash tells you exactly where.