Dereferenzierung eines nicht vertrauenswürdigen Zeigers

Beschreibung

Dereferenzierung eines nicht vertrauenswürdigen Zeigers tritt auf, wenn Software einen Wert aus einer nicht vertrauenswürdigen Quelle erhält, diesen Wert in einen Pointer konvertiert oder als Pointer verwendet und dann den Pointer dereferenziert. Angreifer, die den Zeigerwert kontrollieren oder beeinflussen können, können ihn auf beliebige Speicherorte richten und dadurch sensible Daten lesen, Speicher beschädigen, Abstürze verursachen oder beliebigen Code ausführen. Dies ist besonders gefährlich, wenn der nicht vertrauenswürdige Wert direkt als Funktionszeiger verwendet wird, wenn Kernel-Code Pointer aus dem Benutzerbereich dereferenziert oder wenn Software, die für vertrauenswürdige Umgebungen entwickelt wurde, Netzwerkeingaben ausgesetzt ist.

Risiko

Diese Schwachstelle kann zur vollständigen Systemkompromittierung führen. Wenn Angreifer einen Zeigerwert kontrollieren, können sie von beliebigen Speicherorten lesen, um sensible Daten wie kryptografische Schlüssel oder Passwörter zu stehlen. Schreiben durch einen kontrollierten Pointer ermöglicht die Beschädigung kritischer Datenstrukturen, Funktionszeiger oder Rücksprungadressen und ermöglicht Codeausführung. In Kernel- oder privilegierten Kontexten wird die Auswirkung verstärkt, da Angreifer auf jeden Speicher im System zugreifen oder ihn modifizieren können. Selbst wenn Codeausführung nicht erreicht wird, verursacht die Dereferenzierung ungültiger Pointer Abstürze, die für Denial of Service ausgenutzt werden können. Die Schwachstelle ist besonders kritisch in C/C++-Code, der Netzwerkeingaben verarbeitet oder nicht vertrauenswürdige Daten bearbeitet.

Lösung

Konvertieren Sie niemals nicht vertrauenswürdige Eingaben direkt in Pointer. Validieren Sie, dass Zeigerwerte in erwartete Bereiche fallen, bevor Sie sie dereferenzieren. Verwenden Sie im Kernel-Code ordnungsgemäße copy_from_user()/copy_to_user()-Funktionen und validieren Sie alle Pointer aus dem Benutzerbereich. Implementieren Sie Grenzprüfung bei Array-Indizes und Offsets, bevor Sie sie in Zeigerarithmetik konvertieren. Verwenden Sie speichersichere Sprachen, wenn möglich. Wenn Sie Indizes in Arrays oder Tabellen verarbeiten, validieren Sie sie gegen die tatsächliche Größe. Für Funktionszeiger verwenden Sie indirekte Tabellen (Switch-Anweisungen oder validierte Indizes) anstatt roher Zeigerwerte aus nicht vertrauenswürdigen Quellen. Aktivieren Sie Laufzeitschutzmaßnahmen wie ASLR, DEP und CFI. Verwenden Sie während der Entwicklung Tools wie AddressSanitizer zur Erkennung von Zeigerproblemen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Wenn der nicht vertrauenswürdige Pointer in einer Leseoperation verwendet wird, können Angreifer möglicherweise sensible Teile des Speichers einschließlich Anmeldedaten, Schlüssel oder privater Daten lesen.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz, Beendigung oder Neustart - Die Dereferenzierung von Zeigern auf unzugängliche oder ungültige Speicherorte verursacht unerwartete Anwendungsbeendigung.
Integrität, Vertraulichkeit, VerfügbarkeitBereich: Integrität, Vertraulichkeit, Verfügbarkeit

Unerlaubten Code oder Befehle ausführen - Wenn der Pointer als Funktionszeiger oder bei einer Schreiboperation verwendet wird, können Angreifer möglicherweise beliebige Codeausführung erreichen.

Beispielcode

Anfälliger Code

// Anfällig: Nicht vertrauenswürdigen Wert direkt als Pointer verwenden
#include <stdint.h>

void vulnerable_read_memory(int socket) {
    uint64_t address;

    // Adresse vom Netzwerk empfangen
    recv(socket, &address, sizeof(address), 0);

    // Anfällig: Direkte Konvertierung eines nicht vertrauenswürdigen Werts in Pointer
    char* ptr = (char*)address;

    // Anfällig: Dereferenzierung eines nicht vertrauenswürdigen Zeigers
    char data = *ptr;  // Angreifer kontrolliert, welcher Speicher gelesen wird

    send(socket, &data, 1, 0);
}
// Anfällig: Kernel-IOCTL mit benutzergeliefertem Pointer
#include <linux/kernel.h>

long vulnerable_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    struct user_request req;
    char *user_buffer;

    if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
        return -EFAULT;

    // Anfällig: req.buffer_ptr kam vom Benutzer und ist nicht validiert
    user_buffer = (char *)req.buffer_ptr;

    // Anfällig: Dereferenzierung eines benutzer-kontrollierten Zeigers im Kernel-Kontext
    char kernel_data = *user_buffer;  // Kann jeden Kernel-Speicher lesen!

    return 0;
}
// Anfällig: Funktionszeiger aus nicht vertrauenswürdiger Quelle
typedef void (*callback_t)(void);

void vulnerable_callback(int socket) {
    uint64_t func_addr;

    recv(socket, &func_addr, sizeof(func_addr), 0);

    // Anfällig: Nicht vertrauenswürdigen Wert als Funktionszeiger verwenden
    callback_t callback = (callback_t)func_addr;

    // Anfällig: Aufruf einer angreifer-kontrollierten Adresse
    callback();  // Beliebige Codeausführung!
}

Korrigierter Code

// Korrigiert: Nie nicht vertrauenswürdige Eingaben in Pointer konvertieren
#include <stdint.h>

// Validierte Indizes in eine bekannte Tabelle anstatt dessen verwenden
static char* valid_buffers[16];
static size_t buffer_count = 0;

int fixed_read_memory(int socket) {
    uint32_t index;

    recv(socket, &index, sizeof(index), 0);

    // Korrigiert: Index gegen bekannte Grenzen validieren
    if (index >= buffer_count) {
        return -1;  // Ungültiger Index
    }

    // Sicher: Validierter Index in kontrolliertes Array verwenden
    char* ptr = valid_buffers[index];
    if (ptr == NULL) {
        return -1;
    }

    char data = *ptr;
    send(socket, &data, 1, 0);
    return 0;
}
// Korrigiert: Ordnungsgemäße Kernel-Pointer-Behandlung
#include <linux/kernel.h>
#include <linux/uaccess.h>

long fixed_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    struct user_request req;
    char user_data;

    if (copy_from_user(&req, (void __user *)arg, sizeof(req)))
        return -EFAULT;

    // Korrigiert: copy_from_user für benutzergelieferte Adressen verwenden
    // Dies validiert, dass die Adresse im Benutzerbereich liegt
    if (!access_ok((void __user *)req.buffer_ptr, 1))
        return -EFAULT;

    if (copy_from_user(&user_data, (void __user *)req.buffer_ptr, 1))
        return -EFAULT;

    // Jetzt enthält user_data sicher kopierte Daten
    process_data(user_data);

    return 0;
}
// Korrigiert: Validierte Funktionstabelle anstatt roher Pointer verwenden
typedef void (*callback_t)(void);

static callback_t valid_callbacks[] = {
    handler_type_0,
    handler_type_1,
    handler_type_2,
    handler_type_3
};
#define NUM_CALLBACKS (sizeof(valid_callbacks) / sizeof(valid_callbacks[0]))

int fixed_callback(int socket) {
    uint32_t callback_index;

    recv(socket, &callback_index, sizeof(callback_index), 0);

    // Korrigiert: Index in Funktionstabelle validieren
    if (callback_index >= NUM_CALLBACKS) {
        return -1;  // Ungültiger Callback
    }

    // Sicher: Validierter Index in kontrollierte Funktionstabelle verwenden
    valid_callbacks[callback_index]();
    return 0;
}

Verwandte CWEs

  • CWE-119: Unzureichende Beschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers (Eltern)
  • CWE-781: Unzureichende Adressvalidierung in IOCTL mit METHOD_NEITHER (kann vorangehen)
  • CWE-125: Out-of-bounds-Lesezugriff (kann folgen)
  • CWE-787: Out-of-bounds-Schreibzugriff (kann folgen)
  • CWE-476: NULL-Pointer-Dereferenzierung (verwandt)
  • CWE-823: Verwendung eines Out-of-range-Pointer-Offsets (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-822: Untrusted Pointer Dereference." https://cwe.mitre.org/data/definitions/822.html
  2. CERT C Secure Coding Standard. "EXP34-C. Do not dereference null pointers."
  3. Microsoft. "Driver Security Checklist." https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/driver-security-checklist