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
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz, Beenden oder Neustart - NULL-Pointer-Dereferenzierung verursacht Programmbeendigung auf den meisten Systemen. |
| Integrität | Bereich: 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. |
| Vertraulichkeit | Bereich: 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
- 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."