Unchecked Return Value to NULL Pointer Dereference
Description
Unchecked Return Value to NULL Pointer Dereference is a compound vulnerability that occurs when software does not verify whether a function returned a NULL pointer before using that pointer. Many functions return NULL to indicate an error condition, such as memory allocation failure or lookup miss. When this error condition is not checked, the code proceeds to dereference the NULL pointer, causing a crash. In rare circumstances on certain architectures where NULL maps to accessible memory at address 0x0, this can lead to memory corruption or code execution vulnerabilities.
Risk
This vulnerability commonly leads to denial of service through application crashes. Attackers can trigger the condition by providing input that causes the underlying function to fail—such as requesting extremely large memory allocations that exceed available resources, or providing malformed data that parsing functions cannot process. While typically causing crashes, on systems where address 0x0 is mapped to accessible memory, NULL pointer dereferences can potentially be exploited for arbitrary memory read/write, leading to code execution. The risk is heightened when user-controlled input influences the function call that returns NULL.
Solution
Always check return values from functions that can return NULL. Verify memory allocation results before use. Check parsing function returns for error conditions. Use compiler warnings that detect unchecked return values. Apply static analysis tools that identify potential NULL dereference paths. Consider using assert statements for development builds. Document and handle all possible error conditions. For critical allocations, implement fallback strategies or graceful failure paths. Use languages or wrappers that enforce NULL checking.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability DoS: Crash, Exit, or Restart - NULL pointer dereference causes program termination on most systems. |
| Integrity | Scope: Integrity, Confidentiality, Availability Execute Unauthorized Code or Commands - In rare cases where NULL address is accessible, attackers may achieve code execution. |
| Confidentiality | Scope: Confidentiality Read Memory - If NULL maps to accessible memory, sensitive data may be read. |
Example Code
Vulnerable Code
// Vulnerable: Unchecked malloc return
#include <stdlib.h>
#include <string.h>
void vulnerable_allocate(size_t size) {
char* buffer = malloc(size);
// Vulnerable: No NULL check before use
memset(buffer, 0, size); // Crashes if malloc returned NULL
// ... use buffer ...
free(buffer);
}
// Vulnerable: Unchecked user-controlled allocation
void vulnerable_process_request(unsigned int user_size) {
// Attacker can provide huge size to cause malloc failure
char* data = malloc(user_size);
// Vulnerable: Dereference without check
data[0] = 'A'; // NULL pointer dereference
strcpy(data, "some data"); // Crash
free(data);
}
// Vulnerable: Unchecked gethostbyaddr (CVE pattern)
#include <netdb.h>
#include <string.h>
void vulnerable_host_lookup(char* user_supplied_addr) {
struct hostent* hp;
char hostname[256];
// Convert address...
struct in_addr addr;
inet_aton(user_supplied_addr, &addr);
// Lookup can return NULL for invalid/unknown address
hp = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET);
// Vulnerable: No NULL check
strcpy(hostname, hp->h_name); // Crash if hp is NULL
}
// Vulnerable: Java NULL pointer without check
public class VulnerableNullCheck {
public void vulnerableUserLookup(String userId) {
// Returns null if user not found
User user = database.findUser(userId);
// Vulnerable: No null check - NullPointerException
String username = user.getUsername(); // Crashes if user is null
processUser(username);
}
public void vulnerableStringOperation() {
String username = getUserName(); // May return null
// Vulnerable: Direct method call on potentially null reference
if (username.equals(ADMIN_USER)) { // NullPointerException
grantAdminAccess();
}
}
public void vulnerableMapLookup(Map<String, Config> configMap, String key) {
// get() returns null if key not found
Config config = configMap.get(key);
// Vulnerable: No null check
String value = config.getValue(); // NullPointerException
}
}
// Vulnerable: Unchecked strtok return
#include <string.h>
void vulnerable_parse(char* input) {
char* token = strtok(input, ":");
// First token might exist...
printf("First: %s\n", token);
// Get second token
token = strtok(NULL, ":");
// Vulnerable: strtok returns NULL if no more tokens
// If input was "single" (no colon), this crashes
printf("Second: %s\n", token); // NULL dereference
}
// Vulnerable: Unchecked realloc
void vulnerable_resize(char** buffer, size_t new_size) {
// Vulnerable: realloc returns NULL on failure
// but original buffer is still valid!
*buffer = realloc(*buffer, new_size);
// If realloc failed, buffer is now NULL
// Original memory is leaked and buffer is unusable
(*buffer)[0] = 'A'; // Crash if realloc failed
}
# Vulnerable: Python dictionary access without check
def vulnerable_dict_access(data, key):
# get() returns None if key doesn't exist
value = data.get(key)
# Vulnerable: Assumes value is not None
return value.strip() # AttributeError if None
def vulnerable_regex_match(pattern, text):
import re
# match() returns None if no match
match = re.match(pattern, text)
# Vulnerable: No None check
return match.group(1) # AttributeError if no match
def vulnerable_list_find(items, predicate):
# next() with filter returns None for empty iterator with default
result = next(filter(predicate, items), None)
# Vulnerable: Assumes result found
return result.process() # AttributeError if None
Fixed Code
// Fixed: Always check malloc return
#include <stdlib.h>
#include <string.h>
int secure_allocate(size_t size) {
// Validate size first
if (size == 0 || size > MAX_ALLOCATION) {
return -1;
}
char* buffer = malloc(size);
// Fixed: Check for NULL
if (buffer == NULL) {
// Handle allocation failure
return -1;
}
memset(buffer, 0, size);
// ... use buffer safely ...
free(buffer);
return 0;
}
// Fixed: Validate and check user-controlled allocation
int secure_process_request(unsigned int user_size) {
// Fixed: Validate size limit
if (user_size > MAX_REQUEST_SIZE) {
return -1;
}
char* data = malloc(user_size);
// Fixed: NULL check
if (data == NULL) {
return -1;
}
data[0] = 'A';
strcpy(data, "some data");
free(data);
return 0;
}
// Fixed: Check gethostbyaddr return
#include <netdb.h>
#include <string.h>
int secure_host_lookup(char* user_supplied_addr, char* hostname, size_t size) {
struct hostent* hp;
struct in_addr addr;
if (inet_aton(user_supplied_addr, &addr) == 0) {
return -1; // Invalid address format
}
hp = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET);
// Fixed: Check for NULL
if (hp == NULL || hp->h_name == NULL) {
return -1; // Lookup failed
}
// Fixed: Use strncpy for safety
strncpy(hostname, hp->h_name, size - 1);
hostname[size - 1] = '\0';
return 0;
}
// Fixed: Java with proper null checks
public class SecureNullCheck {
public void secureUserLookup(String userId) {
User user = database.findUser(userId);
// Fixed: Explicit null check
if (user == null) {
throw new UserNotFoundException("User not found: " + userId);
}
String username = user.getUsername();
processUser(username);
}
public void secureStringOperation() {
String username = getUserName();
// Fixed: Null-safe comparison
if (ADMIN_USER.equals(username)) {
grantAdminAccess();
}
// Or use Objects.equals()
if (Objects.equals(username, ADMIN_USER)) {
grantAdminAccess();
}
}
public void secureMapLookup(Map<String, Config> configMap, String key) {
Config config = configMap.get(key);
// Fixed: Handle null case
if (config == null) {
config = Config.getDefault();
}
String value = config.getValue();
}
// Using Optional (Java 8+)
public Optional<String> secureUserLookupOptional(String userId) {
return Optional.ofNullable(database.findUser(userId))
.map(User::getUsername);
}
}
// Fixed: Check strtok returns
#include <string.h>
int secure_parse(char* input, char* first, char* second, size_t size) {
char* token = strtok(input, ":");
// Fixed: Check first token
if (token == NULL) {
return -1;
}
strncpy(first, token, size - 1);
first[size - 1] = '\0';
// Get second token
token = strtok(NULL, ":");
// Fixed: Check second token
if (token == NULL) {
return -1; // Missing second field
}
strncpy(second, token, size - 1);
second[size - 1] = '\0';
return 0;
}
// Fixed: Safe realloc pattern
int secure_resize(char** buffer, size_t new_size) {
// Fixed: Use temporary variable
char* temp = realloc(*buffer, new_size);
if (temp == NULL) {
// Original buffer still valid - don't lose it
return -1;
}
// Success - update pointer
*buffer = temp;
return 0;
}
# Fixed: Python with proper None checks
def secure_dict_access(data, key):
value = data.get(key)
# Fixed: Handle None
if value is None:
return ""
return value.strip()
def secure_regex_match(pattern, text):
import re
match = re.match(pattern, text)
# Fixed: Check for None
if match is None:
return None
return match.group(1)
def secure_list_find(items, predicate):
result = next(filter(predicate, items), None)
# Fixed: Handle not found case
if result is None:
raise ValueError("No matching item found")
return result.process()
# Using walrus operator (Python 3.8+)
def secure_with_walrus(data, key):
if (value := data.get(key)) is not None:
return value.strip()
return ""
CVE Examples
- CVE-2008-1052: Large Content-Length header triggers malloc failure, leading to NULL dereference.
- CVE-2006-6227: Large message length causes malloc failure and NULL dereference.
- CVE-2006-2555: Missing colon in input causes strtok to return NULL, leading to crash.
- CVE-2003-1054: Missing hostname in Referer header causes NULL dereference.
- CVE-2008-5183: Chain of unchecked return values leading to NULL dereference.
References
- MITRE Corporation. "CWE-690: Unchecked Return Value to NULL Pointer Dereference." https://cwe.mitre.org/data/definitions/690.html
- CERT C Coding Standard. "EXP34-C. Do not dereference null pointers."
- CERT Java Coding Standard. "ERR08-J. Do not catch NullPointerException or any of its ancestors."