Ungeprüfter Rückgabewert zu NULL-Pointer-Dereferenzierung

Beschreibung

Ungeprüfter Rückgabewert zu NULL-Pointer-Dereferenzierung ist eine zusammengesetzte Schwachstelle, die auftritt, wenn Software nicht verifiziert, ob eine Funktion einen NULL-Pointer zurückgegeben hat, bevor dieser Pointer verwendet wird. Viele Funktionen geben NULL zurück, um eine Fehlerbedingung anzuzeigen, wie Speicherallokationsfehler oder fehlgeschlagene Suche. Wenn diese Fehlerbedingung nicht geprüft wird, fährt der Code fort, den NULL-Pointer zu dereferenzieren, was einen Absturz verursacht. In seltenen Fällen auf bestimmten Architekturen, wo NULL auf zugänglichen Speicher an Adresse 0x0 abgebildet wird, kann dies zu Speicherkorruption oder Code-Ausführungsschwachstellen führen.

Risiko

Diese Schwachstelle führt häufig zu Denial of Service durch Anwendungsabstürze. Angreifer können die Bedingung auslösen, indem sie Eingaben bereitstellen, die die zugrundeliegende Funktion zum Scheitern bringen - wie das Anfordern extrem größer Speicherallokationen, die verfügbare Ressourcen übersteigen, oder das Bereitstellen fehlerhafter Daten, die Parsing-Funktionen nicht verarbeiten können. Während dies typischerweise Abstürze verursacht, können auf Systemen, wo Adresse 0x0 auf zugänglichen Speicher abgebildet ist, NULL-Pointer-Dereferenzierungen potenziell für beliebiges Speicherlesen/-schreiben ausgenutzt werden, was zu Code-Ausführung führt. Das Risiko ist erhöt, wenn benutzergesteuerte Eingaben den Funktionsaufruf beeinflussen, der NULL zurückgibt.

Lösung

Prüfen Sie immer Rückgabewerte von Funktionen, die NULL zurückgeben können. Verifizieren Sie Speicherallokationsergebnisse vor der Verwendung. Prüfen Sie Parsing-Funktionsrückgaben auf Fehlerbedingungen. Verwenden Sie Compiler-Warnungen, die ungeprüfte Rückgabewerte erkennen. Wenden Sie statische Analysetools an, die potenzielle NULL-Dereferenzierungspfade identifizieren. Erwägen Sie die Verwendung von assert-Anweisungen für Entwicklungsbuilds. Dokumentieren und behandeln Sie alle möglichen Fehlerbedingungen. Für kritische Allokationen implementieren Sie Fallback-Strategien oder graziose Fehlerpfade. Verwenden Sie Sprachen oder Wrapper, die NULL-Prüfung erzwingen.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz, Beenden oder Neustart - NULL-Pointer-Dereferenzierung verursacht Programmbeendigung auf den meisten Systemen.
IntegritätBereich: Integrität, Vertraulichkeit, Verfügbarkeit

Unbefugten Code oder Befehle ausführen - In seltenen Fällen, wo NULL-Adresse zugänglich ist, können Angreifer Code-Ausführung erreichen.
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Wenn NULL auf zugänglichen Speicher abgebildet ist, können sensible Daten gelesen werden.

Beispielcode

Anfälliger Code

// Anfällig: Ungeprüfter malloc-Rückgabewert
#include <stdlib.h>
#include <string.h>

void vulnerable_allocate(size_t size) {
    char* buffer = malloc(size);

    // Anfällig: Keine NULL-Prüfung vor Verwendung
    memset(buffer, 0, size);  // Stürzt ab wenn malloc NULL zurückgab

    // ... buffer verwenden ...
    free(buffer);
}

// Anfällig: Ungeprüfte benutzergesteuerte Allokation
void vulnerable_process_request(unsigned int user_size) {
    // Angreifer kann riesige Größe angeben um malloc-Fehler zu verursachen
    char* data = malloc(user_size);

    // Anfällig: Dereferenzierung ohne Prüfung
    data[0] = 'A';  // NULL-Pointer-Dereferenzierung

    strcpy(data, "some data");  // Absturz
    free(data);
}

// Anfällig: Ungeprüfter gethostbyaddr (CVE-Muster)
#include <netdb.h>
#include <string.h>

void vulnerable_host_lookup(char* user_supplied_addr) {
    struct hostent* hp;
    char hostname[256];

    // Adresse konvertieren...
    struct in_addr addr;
    inet_aton(user_supplied_addr, &addr);

    // Lookup kann NULL für ungültige/unbekannte Adresse zurückgeben
    hp = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET);

    // Anfällig: Keine NULL-Prüfung
    strcpy(hostname, hp->h_name);  // Absturz wenn hp NULL ist
}
// Anfällig: Java NULL-Pointer ohne Prüfung
public class VulnerableNullCheck {

    public void vulnerableUserLookup(String userId) {
        // Gibt null zurück wenn Benutzer nicht gefunden
        User user = database.findUser(userId);

        // Anfällig: Keine null-Prüfung - NullPointerException
        String username = user.getUsername();  // Stürzt ab wenn user null ist

        processUser(username);
    }

    public void vulnerableStringOperation() {
        String username = getUserName();  // Kann null zurückgeben

        // Anfällig: Direkter Methodenaufruf auf potenziell null Referenz
        if (username.equals(ADMIN_USER)) {  // NullPointerException
            grantAdminAccess();
        }
    }

    public void vulnerableMapLookup(Map<String, Config> configMap, String key) {
        // get() gibt null zurück wenn Schlüssel nicht gefunden
        Config config = configMap.get(key);

        // Anfällig: Keine null-Prüfung
        String value = config.getValue();  // NullPointerException
    }
}
// Anfällig: Ungeprüfter strtok-Rückgabewert
#include <string.h>

void vulnerable_parse(char* input) {
    char* token = strtok(input, ":");

    // Erstes Token könnte existieren...
    printf("Erstes: %s\n", token);

    // Zweites Token holen
    token = strtok(NULL, ":");

    // Anfällig: strtok gibt NULL zurück wenn keine weiteren Tokens
    // Wenn Eingabe "single" war (kein Doppelpunkt), stürzt dies ab
    printf("Zweites: %s\n", token);  // NULL-Dereferenzierung
}

// Anfällig: Ungeprüfter realloc
void vulnerable_resize(char** buffer, size_t new_size) {
    // Anfällig: realloc gibt NULL bei Fehler zurück
    // aber ursprünglicher Puffer ist noch gültig!
    *buffer = realloc(*buffer, new_size);

    // Wenn realloc fehlschlug, ist buffer jetzt NULL
    // Ursprünglicher Speicher ist geleakt und Puffer unbrauchbar
    (*buffer)[0] = 'A';  // Absturz wenn realloc fehlschlug
}
# Anfällig: Python Dictionary-Zugriff ohne Prüfung
def vulnerable_dict_access(data, key):
    # get() gibt None zurück wenn Schlüssel nicht existiert
    value = data.get(key)

    # Anfällig: Nimmt an value ist nicht None
    return value.strip()  # AttributeError wenn None

def vulnerable_regex_match(pattern, text):
    import re

    # match() gibt None zurück wenn kein Match
    match = re.match(pattern, text)

    # Anfällig: Keine None-Prüfung
    return match.group(1)  # AttributeError wenn kein Match

def vulnerable_list_find(items, predicate):
    # next() mit filter gibt None für leeren Iterator mit default zurück
    result = next(filter(predicate, items), None)

    # Anfällig: Nimmt an Ergebnis wurde gefunden
    return result.process()  # AttributeError wenn None

Lösung

// Korrigiert: Immer malloc-Rückgabewert prüfen
#include <stdlib.h>
#include <string.h>

int secure_allocate(size_t size) {
    // Zuerst Größe validieren
    if (size == 0 || size > MAX_ALLOCATION) {
        return -1;
    }

    char* buffer = malloc(size);

    // Korrigiert: Auf NULL prüfen
    if (buffer == NULL) {
        // Allokationsfehler behandeln
        return -1;
    }

    memset(buffer, 0, size);
    // ... buffer sicher verwenden ...
    free(buffer);
    return 0;
}

// Korrigiert: Benutzergesteuerte Allokation validieren und prüfen
int secure_process_request(unsigned int user_size) {
    // Korrigiert: Größenlimit validieren
    if (user_size > MAX_REQUEST_SIZE) {
        return -1;
    }

    char* data = malloc(user_size);

    // Korrigiert: NULL-Prüfung
    if (data == NULL) {
        return -1;
    }

    data[0] = 'A';
    strcpy(data, "some data");
    free(data);
    return 0;
}

// Korrigiert: gethostbyaddr-Rückgabewert prüfen
#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;  // Ungültiges Adressformat
    }

    hp = gethostbyaddr(&addr, sizeof(struct in_addr), AF_INET);

    // Korrigiert: Auf NULL prüfen
    if (hp == NULL || hp->h_name == NULL) {
        return -1;  // Lookup fehlgeschlagen
    }

    // Korrigiert: strncpy für Sicherheit verwenden
    strncpy(hostname, hp->h_name, size - 1);
    hostname[size - 1] = '\0';
    return 0;
}
// Korrigiert: Java mit korrekten null-Prüfungen
public class SecureNullCheck {

    public void secureUserLookup(String userId) {
        User user = database.findUser(userId);

        // Korrigiert: Explizite null-Prüfung
        if (user == null) {
            throw new UserNotFoundException("Benutzer nicht gefunden: " + userId);
        }

        String username = user.getUsername();
        processUser(username);
    }

    public void secureStringOperation() {
        String username = getUserName();

        // Korrigiert: Null-sicherer Vergleich
        if (ADMIN_USER.equals(username)) {
            grantAdminAccess();
        }

        // Oder Objects.equals() verwenden
        if (Objects.equals(username, ADMIN_USER)) {
            grantAdminAccess();
        }
    }

    public void secureMapLookup(Map<String, Config> configMap, String key) {
        Config config = configMap.get(key);

        // Korrigiert: null-Fall behandeln
        if (config == null) {
            config = Config.getDefault();
        }

        String value = config.getValue();
    }

    // Optional verwenden (Java 8+)
    public Optional<String> secureUserLookupOptional(String userId) {
        return Optional.ofNullable(database.findUser(userId))
                       .map(User::getUsername);
    }
}
// Korrigiert: strtok-Rückgabewerte prüfen
#include <string.h>

int secure_parse(char* input, char* first, char* second, size_t size) {
    char* token = strtok(input, ":");

    // Korrigiert: Erstes Token prüfen
    if (token == NULL) {
        return -1;
    }
    strncpy(first, token, size - 1);
    first[size - 1] = '\0';

    // Zweites Token holen
    token = strtok(NULL, ":");

    // Korrigiert: Zweites Token prüfen
    if (token == NULL) {
        return -1;  // Zweites Feld fehlt
    }
    strncpy(second, token, size - 1);
    second[size - 1] = '\0';

    return 0;
}

// Korrigiert: Sicheres realloc-Muster
int secure_resize(char** buffer, size_t new_size) {
    // Korrigiert: Temporäre Variable verwenden
    char* temp = realloc(*buffer, new_size);

    if (temp == NULL) {
        // Ursprünglicher Puffer noch gültig - nicht verlieren
        return -1;
    }

    // Erfolg - Pointer aktualisieren
    *buffer = temp;
    return 0;
}
# Korrigiert: Python mit korrekten None-Prüfungen
def secure_dict_access(data, key):
    value = data.get(key)

    # Korrigiert: None behandeln
    if value is None:
        return ""

    return value.strip()

def secure_regex_match(pattern, text):
    import re

    match = re.match(pattern, text)

    # Korrigiert: Auf None prüfen
    if match is None:
        return None

    return match.group(1)

def secure_list_find(items, predicate):
    result = next(filter(predicate, items), None)

    # Korrigiert: Nicht-gefunden-Fall behandeln
    if result is None:
        raise ValueError("Kein passendes Element gefunden")

    return result.process()

# Mit Walrus-Operator (Python 3.8+)
def secure_with_walrus(data, key):
    if (value := data.get(key)) is not None:
        return value.strip()
    return ""

CVE-Beispiele

  • CVE-2008-1052: Größer Content-Length Header löst malloc-Fehler aus, führt zu NULL-Dereferenzierung.
  • CVE-2006-6227: Größe Nachrichtenlänge verursacht malloc-Fehler und NULL-Dereferenzierung.
  • CVE-2006-2555: Fehlender Doppelpunkt in Eingabe veranlasst strtok NULL zurückzugeben, führt zu Absturz.
  • CVE-2003-1054: Fehlender Hostname in Referer-Header verursacht NULL-Dereferenzierung.
  • CVE-2008-5183: Kette von ungeprüften Rückgabewerten führt zu NULL-Dereferenzierung.

Referenzen

  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."