NULL-Pointer-Dereferenzierung

Beschreibung

NULL-Pointer-Dereferenzierung tritt auf, wenn eine Anwendung einen Pointer dereferenziert, von dem sie erwartet, dass er gültig ist, der aber tatsächlich NULL ist, was typischerweise einen Absturz oder Programmende verursacht. Eine NULL-Pointer-Dereferenzierung geschieht, wenn Code versucht, über einen Pointer, der nicht initialisiert wurde, auf NULL gesetzt wurde oder dessen referenzierter Speicher freigegeben wurde, Speicher zu lesen oder zu schreiben. In Sprachen wie C und C++ ist das Dereferenzieren eines NULL-Zeigers undefiniertes Verhalten, das normalerweise zu einem Segmentierungsfehler oder einer Zugriffsverletzung führt. Obwohl historisch hauptsächlich als Zuverlässigkeitsproblem betrachtet, können NULL-Pointer-Dereferenzierungen unter bestimmten Bedingungen manchmal für Codeausführung ausgenutzt werden.

Risiko

NULL-Pointer-Dereferenzierung ist durchgehend in den CWE Top 25 der gefährlichsten Software-Schwachstellen gelistet. CVE-2025-49694 in Microsoft Windows Server 2025 ermöglicht Privilegieneskalation durch eine NULL-Pointer-Dereferenzierung in der Brokering File System-Komponente (CVSS 7.8). CVE-2025-46404 in Entr'ouvert Lasso ermöglicht Remote-Denial-of-Service durch malformierte SAML-Antworten. CVE-2025-42902 in SAP NetWeaver AS ABAP ermöglicht nicht authentifizierten Angreifern, SAP-Arbeitsprozesse zum Absturz zu bringen. Während die meisten NULL-Dereferenzierungen Denial of Service verursachen, können einige für Codeausführung ausgenutzt werden, insbesondere in Kombination mit Speicher-Mapping an Adresse 0 oder im Kernel-Bereich.

Lösung

Validieren Sie immer Pointer vor dem Dereferenzieren. Initialisieren Sie Pointer auf NULL und prüfen Sie vor der Verwendung. Verwenden Sie statische Analysewerkzeuge, um potenzielle NULL-Dereferenzierungen zu erkennen. Implementieren Sie defensive Programmierpraktiken mit expliziten NULL-Prüfungen nach Allokation oder beim Empfang von Zeigern aus externen Quellen. Verwenden Sie Smart Pointer in C++, um Speicher sicher zu verwalten. Aktivieren Sie Compiler-Warnungen für NULL-Pointer-Probleme. In kritischen Systemen erwägen Sie die Verwendung von Sprachen mit eingebauter NULL-Sicherheit (Rust, Kotlin, Swift). Mappen Sie die NULL-Adresse als nicht zugänglich, um Ausnutzung zu verhindern.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitUmfang: Denial of Service

NULL-Pointer-Dereferenzierung bringt typischerweise die Anwendung zum Absturz, was zu Dienstunterbrechung führt.
IntegritätUmfang: Codeausführung

In seltenen Fällen, besonders in Kernel-Code, können NULL-Dereferenzierungen für beliebige Codeausführung ausgenutzt werden.
ZugriffskontrolleUmfang: Privilegieneskalation

Kernel-NULL-Pointer-Dereferenzierungen können zu lokaler Privilegieneskalation führen.

Beispielcode

Anfälliger Code

// ANFÄLLIG: Keine NULL-Prüfung nach malloc
#include <stdlib.h>
#include <string.h>

void process_data(size_t size) {
    char* buffer = malloc(size);
    // malloc kann NULL zurückgeben wenn Allokation fehlschlägt!
    strcpy(buffer, "data");  // NULL-Dereferenzierung wenn malloc fehlschlug
}

// ANFÄLLIG: Keine NULL-Prüfung auf Funktionsrückgabewert
struct User* find_user(int id);

void update_user(int user_id) {
    struct User* user = find_user(user_id);
    // find_user könnte NULL zurückgeben wenn Benutzer nicht gefunden!
    user->status = ACTIVE;  // NULL-Dereferenzierungs-Absturz
}

// ANFÄLLIG: Freigegebenen Pointer dereferenzieren
void cleanup(struct Resource* res) {
    free(res->data);
    // res->data ist jetzt ungültig (Use-after-free Möglichkeit)
    printf("Daten: %s\n", res->data);  // Undefiniertes Verhalten
}
// ANFÄLLIG: Optional/Pointer nicht prüfen
class UserManager {
public:
    User* findUser(int id) {
        auto it = users_.find(id);
        if (it == users_.end()) return nullptr;
        return &it->second;
    }

    void updateUserEmail(int id, const std::string& email) {
        User* user = findUser(id);
        // Keine NULL-Prüfung - Absturz wenn Benutzer nicht gefunden!
        user->email = email;
    }
};

// ANFÄLLIG: Exception könnte Pointer NULL lassen
void initializeResource() {
    Resource* resource = nullptr;
    try {
        resource = createResource();  // Kann werfen
    } catch (...) {
        // resource ist immer noch NULL
    }
    resource->initialize();  // NULL-Dereferenzierung wenn Exception auftrat
}
// ANFÄLLIG: Null Integer unboxen
public void processAge(Integer age) {
    // age könnte null sein
    int ageValue = age;  // NullPointerException wenn age null ist
    System.out.println("Alter: " + ageValue);
}

// ANFÄLLIG: Methodenkette ohne Null-Prüfungen
public String getUserCity(Order order) {
    // Jedes davon kann null sein!
    return order.getUser().getAddress().getCity();
}

Korrigierter Code

// SICHER: Nach Allokation auf NULL prüfen
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int process_data_safe(size_t size) {
    if (size == 0 || size > MAX_BUFFER_SIZE) {
        return -EINVAL;
    }

    char* buffer = malloc(size);
    if (buffer == NULL) {
        // Allokationsfehler ordnungsgemäß behandeln
        return -ENOMEM;
    }

    // Sicher buffer jetzt zu verwenden
    strncpy(buffer, "data", size - 1);
    buffer[size - 1] = '\0';

    // ... buffer verwenden ...

    free(buffer);
    return 0;
}

// SICHER: Funktionsrückgabewert prüfen
struct User* find_user(int id);

int update_user_safe(int user_id) {
    struct User* user = find_user(user_id);

    if (user == NULL) {
        // Benutzer-nicht-gefunden-Fall behandeln
        log_error("Benutzer %d nicht gefunden", user_id);
        return -ENOENT;
    }

    user->status = ACTIVE;
    return 0;
}

// SICHER: Pointer nach free auf NULL setzen
void cleanup_safe(struct Resource* res) {
    if (res == NULL) return;

    if (res->data != NULL) {
        free(res->data);
        res->data = NULL;  // Use-after-free verhindern
    }
}
// SICHER: Optional verwenden und vor Verwendung prüfen
#include <optional>
#include <memory>

class UserManager {
public:
    std::optional<std::reference_wrapper<User>> findUser(int id) {
        auto it = users_.find(id);
        if (it == users_.end()) return std::nullopt;
        return std::ref(it->second);
    }

    bool updateUserEmail(int id, const std::string& email) {
        auto userOpt = findUser(id);
        if (!userOpt.has_value()) {
            return false;  // Benutzer nicht gefunden
        }
        userOpt->get().email = email;
        return true;
    }
};

// SICHER: Smart Pointer verwenden
void processResource() {
    auto resource = std::make_unique<Resource>();
    // Keine NULL-Prüfung nötig - make_unique wirft bei Fehler
    resource->initialize();
}  // Automatische Bereinigung

// SICHER: RAII-Muster mit Exception-Sicherheit
class ResourceGuard {
public:
    ResourceGuard() : resource_(createResource()) {
        if (!resource_) {
            throw std::runtime_error("Ressource könnte nicht erstellt werden");
        }
    }

    Resource& get() { return *resource_; }

private:
    std::unique_ptr<Resource> resource_;
};
// SICHER: Explizite Null-Prüfung
public void processAge(Integer age) {
    if (age == null) {
        throw new IllegalArgumentException("Alter darf nicht null sein");
    }
    int ageValue = age;
    System.out.println("Alter: " + ageValue);
}

// SICHER: Optional verwenden
public Optional<String> getUserCity(Order order) {
    return Optional.ofNullable(order)
        .map(Order::getUser)
        .map(User::getAddress)
        .map(Address::getCity);
}

// SICHER: Null-sichere Methodenkette
public String getUserCitySafe(Order order) {
    if (order == null) return "Unbekannt";
    User user = order.getUser();
    if (user == null) return "Unbekannt";
    Address address = user.getAddress();
    if (address == null) return "Unbekannt";
    return address.getCity() != null ? address.getCity() : "Unbekannt";
}

// SICHER: Objects.requireNonNull für Parameter verwenden
public void createUser(String name, String email) {
    this.name = Objects.requireNonNull(name, "Name darf nicht null sein");
    this.email = Objects.requireNonNull(email, "E-Mail darf nicht null sein");
}

Ausgenutzt in der Praxis

Microsoft Windows Server 2025 (Microsoft, 2025)

CVE-2025-49694 in Microsoft Windows Server 2025 Brokering File System ermöglicht lokalen Angreifern Privilegieneskalation durch NULL-Pointer-Dereferenzierung, bewertet mit CVSS 7.8 hoher Schweregrad mit Auswirkungen auf Vertraulichkeit, Integrität und Verfügbarkeit.

Entr'ouvert Lasso SAML-Bibliothek (Entr'ouvert, 2025)

CVE-2025-46404 in Entr'ouvert Lasso 2.5.1 ermöglicht nicht authentifizierten Remote-Angreifern Denial of Service durch Senden malformierter SAML-Antworten, die NULL-Pointer-Dereferenzierung während der Signaturverifizierung auslösen.

SAP NetWeaver AS ABAP (SAP, 2025)

CVE-2025-42902 in SAP NetWeaver ermöglicht nicht authentifizierten Angreifern, SAP-Arbeitsprozesse zum Absturz zu bringen durch Senden korrupter SAP Logon- oder Assertion-Tickets, die NULL-Pointer-Dereferenzierung auslösen.


Werkzeuge zum Testen/Ausnutzen

  • Valgrind — Speicherfehler einschließlich NULL-Dereferenzierungen erkennen.

  • AddressSanitizer — Speicherfehler zur Laufzeit erkennen.

  • Coverity — statische Analyse zur NULL-Dereferenzierungs-Erkennung.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-476: NULL Pointer Dereference." https://cwe.mitre.org/data/definitions/476.html

  2. CERT. "EXP34-C. Do not dereference null pointers." https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers