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

ImpactDetails
AvailabilityScope: Availability

DoS: Crash, Exit, or Restart - NULL pointer dereference causes program termination on most systems.
IntegrityScope: Integrity, Confidentiality, Availability

Execute Unauthorized Code or Commands - In rare cases where NULL address is accessible, attackers may achieve code execution.
ConfidentialityScope: 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

  1. MITRE Corporation. "CWE-690: Unchecked Return Value to NULL Pointer Dereference." https://cwe.mitre.org/data/definitions/690.html
  2. CERT C Coding Standard. "EXP34-C. Do not dereference null pointers."
  3. CERT Java Coding Standard. "ERR08-J. Do not catch NullPointerException or any of its ancestors."