Zugriff auf Speicherort nach Pufferende

Beschreibung

Zugriff auf Speicherort nach Pufferende ist eine Speichersicherheitsschwachstelle, bei der Software von einem Speicherort liest oder in einen schreibt, der über das Ende eines allozierten Puffers hinausgeht. Dies tritt typischerweise auf, wenn ein Pointer oder Array-Index über das Pufferende hinaus inkrementiert wird, wenn Zeigerarithmetik einen Offset über den gültigen Bereich hinaus erzeugt, oder wenn die zum Kopieren oder Zugreifen verwendete Größe die tatsächliche Puffergröße überschreitet. Dies ist eine der häufigsten und gefährlichsten Schwachstellenklassen, die klassische Buffer Overflows umfasst.

Risiko

Zugriff auf Speicher über ein Pufferende hinaus ist extrem gefährlich. Schreiben über das Pufferende (Buffer Overflow) kann angrenzende Daten einschließlich Funktions-Rücksprungadressen, Funktionszeiger, Heap-Metadaten oder Sicherheitsflags überschreiben und potenziell beliebige Code-Ausführung ermöglichen. Lesen über das Pufferende kann sensible Informationen aus angrenzendem Speicher exponieren, einschließlich Passwörter, kryptografische Schlüssel oder Speicherlayout-Informationen, die nützlich sind um ASLR zu umgehen. Diese Schwachstellenklasse war für unzählige kritische Sicherheits-Exploits in allen Arten von Software verantwortlich.

Lösung

Validieren Sie immer, dass Pufferzugriffe innerhalb der Grenzen bleiben. Verwenden Sie größenprüfende String-Funktionen (strncpy, snprintf) anstelle unbegrenzter Versionen (strcpy, sprintf). Validieren Sie alle Größenparameter und Array-Indizes vor der Verwendung. Verwenden Sie Compiler-bereitgestellte Buffer-Overflow-Schutzmassnahmen (Stack Canaries, FORTIFY_SOURCE). Aktivieren Sie Address Space Layout Randomization (ASLR) und Data Execution Prevention (DEP). Verwenden Sie speichersichere Sprachen oder Container mit automatischem Bounds-Checking. Setzen Sie statische Analysetools und dynamisches Testen (AddressSanitizer, Valgrind) während der Entwicklung ein.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Out-of-bounds Reads exponieren sensible Daten aus angrenzendem Speicher.
IntegritätBereich: Integrität

Speicher ändern - Buffer Overflows korrumpieren angrenzende Datenstrukturen, Funktionszeiger oder Rücksprungadressen.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz - Speicherkorruption verursacht typischerweise Abstürze.
IntegritätBereich: Integrität, Vertraulichkeit, Verfügbarkeit

Unbefugten Code ausführen - Angreifer können beliebige Code-Ausführung durch Buffer-Overflow-Exploitation erreichen.

Beispielcode

Verwundbarer Code

// Verwundbar: Klassischer Buffer Overflow mit strcpy
void vulnerable_host_lookup(char *user_supplied_addr) {
    struct hostent *hp;
    char hostname[64];  // Puffer fester Größe

    // Annahme: Validierung prüft nur Adressformat, nicht Länge
    validate_addr_form(user_supplied_addr);

    hp = gethostbyaddr(inet_addr(user_supplied_addr),
                       sizeof(struct in_addr), AF_INET);

    // Verwundbar: Keine Größenprüfung vor Kopieren
    strcpy(hostname, hp->h_name);  // Overflow wenn h_name > 64 Zeichen
}
// Verwundbar: Lesen über Puffer mit Längen-Mismatch
void vulnerable_process_data(char* data, int declared_length) {
    char buffer[256];

    // Verwundbar: Benutzerbereitgestellte Länge ohne Validierung vertrauen
    for (int i = 0; i < declared_length; i++) {
        buffer[i] = data[i];  // Overflow wenn declared_length > 256
    }
}

// Verwundbar: Off-by-one Overflow
char* vulnerable_copy_string(const char* src) {
    size_t len = strlen(src);
    char* dest = malloc(len);  // Verwundbar: +1 für Null-Terminator vergessen

    strcpy(dest, src);  // Schreibt len+1 Bytes, Overflow um 1
    return dest;
}
// Verwundbar: Netzwerkdaten-Länge vertrauen
typedef struct {
    uint32_t length;
    char data[];
} NetworkPacket;

void vulnerable_process_packet(char* raw_packet) {
    NetworkPacket* packet = (NetworkPacket*)raw_packet;
    char local_buffer[1024];

    // Verwundbar: Längenfeld aus angreifergesteuerten Daten vertrauen
    memcpy(local_buffer, packet->data, packet->length);  // Overflow!
}

Gefixter Code

// Gefixt: Größenlimitiertes String-Kopieren
void fixed_host_lookup(char *user_supplied_addr) {
    struct hostent *hp;
    char hostname[64];

    validate_addr_form(user_supplied_addr);

    hp = gethostbyaddr(inet_addr(user_supplied_addr),
                       sizeof(struct in_addr), AF_INET);

    if (hp == NULL) return;

    // Gefixt: strncpy mit explizitem Größenlimit verwenden
    strncpy(hostname, hp->h_name, sizeof(hostname) - 1);
    hostname[sizeof(hostname) - 1] = '\0';  // Null-Terminierung sicherstellen
}
// Gefixt: Länge vor Verwendung validieren
void fixed_process_data(char* data, int declared_length, int actual_data_size) {
    char buffer[256];

    // Gefixt: Länge gegen Puffer und tatsächliche Daten validieren
    if (declared_length <= 0 || declared_length > 256) {
        return;  // Ungültige Länge
    }
    if (declared_length > actual_data_size) {
        return;  // Deklarierte Länge überschreitet verfügbare Daten
    }

    memcpy(buffer, data, declared_length);
}

// Gefixt: Korrekte Allokationsgröße
char* fixed_copy_string(const char* src) {
    size_t len = strlen(src);
    char* dest = malloc(len + 1);  // Gefixt: +1 für Null-Terminator

    if (dest == NULL) return NULL;

    strcpy(dest, src);  // Jetzt sicher
    return dest;
}
// Gefixt: Netzwerkdaten-Länge validieren
#define MAX_PACKET_DATA 1024

int fixed_process_packet(char* raw_packet, size_t raw_packet_size) {
    // Gefixt: Paketstruktur validieren
    if (raw_packet_size < sizeof(NetworkPacket)) {
        return -1;  // Paket zu klein
    }

    NetworkPacket* packet = (NetworkPacket*)raw_packet;

    // Gefixt: Längenfeld validieren
    uint32_t data_length = ntohl(packet->length);

    if (data_length > MAX_PACKET_DATA) {
        return -1;  // Länge überschreitet Maximum
    }

    if (data_length > raw_packet_size - sizeof(NetworkPacket)) {
        return -1;  // Länge überschreitet verfügbare Daten
    }

    char local_buffer[MAX_PACKET_DATA];
    memcpy(local_buffer, packet->data, data_length);

    return 0;
}

CVE-Beispiele

  • CVE-2009-2550: Stack-basierter Buffer Overflow in Media Player über langen Playlist-Eintrag.
  • CVE-2009-2403: Heap-basierter Buffer Overflow in Media Player über langen Playlist-Eintrag.
  • CVE-2009-0689: Größer Präzisionswert in Format-String löst Buffer Overflow aus.
  • CVE-2009-0558: Angreifergesteuerter Array-Index führt zu Code-Ausführung.

Referenzen

  1. MITRE Corporation. "CWE-788: Access of Memory Location After End of Buffer." https://cwe.mitre.org/data/definitions/788.html
  2. CERT C Coding Standard. "ARR30-C. Do not form or use out-of-bounds pointers or array subscripts."
  3. CWE. "CWE-787: Out-of-bounds Write." Verwandte Schwachstelle.