Unsachgemäße Einschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers

Beschreibung

Unsachgemäße Einschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers tritt auf, wenn Software Operationen an einem Speicherpuffer ausführt, aber aus einem Speicherbereich lesen oder in einen Speicherbereich schreiben kann, der außerhalb der beabsichtigten Grenze des Puffers liegt. Dies ist die übergeordnete Kategorie für viele spezifische Pufferfehlertypen, einschließlich Buffer Overflows (CWE-120), Buffer Underflows, Out-of-Bounds-Leseoperationen (CWE-125) und Out-of-Bounds-Schreiboperationen (CWE-787). Diese Schwachstellen treten auf, wenn Array-Indizes, Pointer-Arithmetik oder Größenberechnungen den Zugriff über die zugewiesenen Speicherbereiche hinaus ermöglichen.

Risiko

Speicherpufferfehler gehören konstant zu den am häufigsten ausgenutzten Schwachstellenklassen. Out-of-Bounds-Schreiboperationen können angrenzenden Speicher überschreiben, einschließlich Rücksprungadressen, Funktionszeigern und sicherheitskritischen Daten. Out-of-Bounds-Leseoperationen können sensible Informationen preisgeben, einschließlich kryptographischer Schlüssel und Speicherlayout (wodurch ASLR umgangen wird). Moderne Schutzmaßnahmen (ASLR, DEP, Stack Canaries) haben die Ausnutzung erschwert, aber nicht unmöglich gemacht. Browser-Exploits, Kernel-Schwachstellen und Firmware-Angriffe nutzen häufig Pufferfehler aus.

Lösung

Verwenden Sie speichersichere Sprachen (Rust, Go, Java, Python), wenn möglich. In C/C++ verwenden Sie Funktionen mit Grenzprüfung und sichere String-Bibliotheken. Validieren Sie alle Array-Indizes und Puffergrößen. Nutzen Sie Compiler-Schutzmechanismen (Stack Canaries, ASLR, DEP). Setzen Sie statische und dynamische Analyse-Tools ein. Verwenden Sie AddressSanitizer während der Entwicklung. Erwägen Sie die Verwendung sicherer Container (std::vector, std::string) in C++. Implementieren Sie Defense-in-Depth mit mehreren Schutzebenen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Informationspreisgabe

Out-of-Bounds-Leseoperationen können sensible Daten aus angrenzenden Speicherbereichen preisgeben.
IntegritätBereich: Code-Ausführung

Out-of-Bounds-Schreiboperationen können Code-Pointer beschädigen und beliebige Code-Ausführung ermöglichen.
VerfügbarkeitBereich: Denial of Service

Pufferfehler verursachen typischerweise Abstürze und können für DoS-Angriffe genutzt werden.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Keine Grenzprüfung beim Array-Zugriff
void process_data(int *data, int index, int value) {
    // Keine Validierung des Index
    data[index] = value;  // Out-of-Bounds-Schreiboperation bei ungültigem Index
}

// VERWUNDBAR: Buffer Overflow beim String-Kopieren
void copy_user_input(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // Überlauf wenn input > 63 Zeichen
}

// VERWUNDBAR: Off-by-One in der Schleife
void fill_array(int *arr, int size) {
    // Fehler: <= statt <
    for (int i = 0; i <= size; i++) {
        arr[i] = 0;  // Schreibt ein Element über das Ende hinaus
    }
}

// VERWUNDBAR: Integer-Überlauf bei der Größenberechnung
void allocate_matrix(unsigned int rows, unsigned int cols) {
    // Multiplikation kann überlaufen
    unsigned int size = rows * cols * sizeof(int);
    int *matrix = malloc(size);  // Kann einen zu kleinen Puffer zuweisen
    // Nachfolgender Zugriff verursacht Überlauf
}

// VERWUNDBAR: Out-of-Bounds-Lesevorgang
char get_character(char *str, int index) {
    // Keine Grenzprüfung
    return str[index];  // Kann über die String-Grenzen hinaus lesen
}
// VERWUNDBAR: Pointer-Arithmetik ohne Grenzen
void process_packet(unsigned char *packet, size_t length) {
    unsigned char *ptr = packet;
    unsigned char *end = packet + length;

    while (*ptr != 0) {  // Kann über das Paketende hinaus lesen
        process_byte(*ptr);
        ptr++;
    }
}

// VERWUNDBAR: Heap Buffer Overflow
void resize_buffer(Buffer *buf, size_t new_size) {
    // Wenn new_size > zugewiesene Größe, kommt es zum Schreibüberlauf
    buf->size = new_size;
    memset(buf->data + buf->used, 0, new_size - buf->used);
}

// VERWUNDBAR: Stack-Puffer-Leseüberlauf
void log_data(char *data, size_t len) {
    char buffer[256];
    // Wenn len > 256, wird zu viel kopiert, dann über den Puffer hinaus gelesen
    memcpy(buffer, data, len);
    printf("Data: %s\n", buffer);  // Kann über den Puffer hinaus lesen, wenn kein Null-Terminator vorhanden
}
// VERWUNDBAR: C++ mit rohen Arrays
class DataStore {
    int data[100];

public:
    int& get(size_t index) {
        return data[index];  // Keine Grenzprüfung
    }

    void set(size_t index, int value) {
        data[index] = value;  // Keine Grenzprüfung
    }
};

// VERWUNDBAR: Iterator-Invalidierung
void remove_items(std::vector<int>& vec) {
    for (auto it = vec.begin(); it != vec.end(); ++it) {
        if (*it < 0) {
            vec.erase(it);  // Invalidiert den Iterator!
            // Weitere Verwendung ist undefiniertes Verhalten
        }
    }
}

Sichere Lösung

// SICHER: Grenzprüfung beim Array-Zugriff
bool process_data_safe(int *data, size_t data_size, size_t index, int value) {
    if (index >= data_size) {
        return false;  // Ausserhalb der Grenzen
    }
    data[index] = value;
    return true;
}

// SICHER: Grenzgeprüfte String-Kopie
void copy_user_input_safe(const char *input) {
    char buffer[64];
    // strncpy begrenzt die Kopierlänge
    strncpy(buffer, input, sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';  // Null-Terminierung sicherstellen
}

// SICHER: Korrekte Schleifengrenzen
void fill_array_safe(int *arr, size_t size) {
    for (size_t i = 0; i < size; i++) {
        arr[i] = 0;
    }
}

// SICHER: Integer-Überlaufprüfung
int *allocate_matrix_safe(size_t rows, size_t cols) {
    // Auf Überlauf prüfen
    if (rows > 0 && cols > SIZE_MAX / rows) {
        return NULL;
    }
    size_t count = rows * cols;
    if (count > SIZE_MAX / sizeof(int)) {
        return NULL;
    }

    size_t size = count * sizeof(int);
    return malloc(size);
}

// SICHER: Grenzgeprüfte Zeichenzugriff
bool get_character_safe(const char *str, size_t str_len, size_t index, char *out) {
    if (index >= str_len) {
        return false;
    }
    *out = str[index];
    return true;
}
// SICHER: Pointer-Arithmetik mit Grenzprüfung
void process_packet_safe(unsigned char *packet, size_t length) {
    if (length == 0) return;

    unsigned char *ptr = packet;
    unsigned char *end = packet + length;

    while (ptr < end && *ptr != 0) {
        process_byte(*ptr);
        ptr++;
    }
}

// SICHER: Validierte Puffergrößenänderung
bool resize_buffer_safe(Buffer *buf, size_t new_size) {
    if (new_size > buf->allocated) {
        // Neuzuweisung erforderlich
        void *new_data = realloc(buf->data, new_size);
        if (!new_data) return false;
        buf->data = new_data;
        buf->allocated = new_size;
    }

    if (new_size > buf->used) {
        memset(buf->data + buf->used, 0, new_size - buf->used);
    }
    buf->size = new_size;
    return true;
}

// SICHER: Grenzgeprüfte Protokollierung
void log_data_safe(const char *data, size_t len) {
    char buffer[256];
    size_t copy_len = (len < sizeof(buffer) - 1) ? len : sizeof(buffer) - 1;

    memcpy(buffer, data, copy_len);
    buffer[copy_len] = '\0';  // Immer Null-terminieren

    printf("Data: %s\n", buffer);
}
// SICHER: std::array mit Grenzprüfung verwenden
#include <array>
#include <stdexcept>

class DataStoreSafe {
    std::array<int, 100> data;

public:
    int& get(size_t index) {
        return data.at(index);  // Wirft std::out_of_range
    }

    void set(size_t index, int value) {
        data.at(index) = value;  // Wirft std::out_of_range
    }

    size_t size() const { return data.size(); }
};

// SICHER: std::vector mit at() verwenden
void process_vector_safe(std::vector<int>& vec, size_t index) {
    try {
        int value = vec.at(index);  // Grenzgeprüft
        process(value);
    } catch (const std::out_of_range& e) {
        handle_error("Index out of range");
    }
}

// SICHER: Korrekte Iterator-Behandlung
void remove_items_safe(std::vector<int>& vec) {
    // Erase-Remove-Idiom verwenden
    vec.erase(
        std::remove_if(vec.begin(), vec.end(),
            [](int x) { return x < 0; }),
        vec.end()
    );
}

// Alternative: Sorgfältige Iterator-Aktualisierung
void remove_items_safe_alt(std::vector<int>& vec) {
    for (auto it = vec.begin(); it != vec.end(); /* kein Inkrement */) {
        if (*it < 0) {
            it = vec.erase(it);  // erase gibt den nächsten gültigen Iterator zurück
        } else {
            ++it;
        }
    }
}
// Rust: Speichersicherheit standardmäßig
fn process_data_rust(data: &mut [i32], index: usize, value: i32) -> Result<(), &'static str> {
    // Grenzprüfung erfolgt automatisch
    if let Some(elem) = data.get_mut(index) {
        *elem = value;
        Ok(())
    } else {
        Err("Index out of bounds")
    }
}

// Rust: Sichere Iteration
fn remove_negatives(vec: &mut Vec<i32>) {
    vec.retain(|&x| x >= 0);  // Sicheres Entfernen
}

Ausgenutzt in der Praxis

Heartbleed (2014)

CVE-2014-0160 war ein Out-of-Bounds-Lesevorgang in OpenSSL, der bis zu 64 KB Serverspeicher pro Anfrage preisgab, einschließlich potenziell privater Schlüssel und Benutzerdaten.

EternalBlue (2017)

CVE-2017-0144 war ein Buffer Overflow in Windows SMBv1, der von der WannaCry- und NotPetya-Ransomware ausgenutzt wurde und weltweit Schäden in Milliardenhöhe verursachte.

Chrome V8 Buffer Overflows

Zahlreiche Buffer-Overflow-Schwachstellen in Chromes V8-JavaScript-Engine wurden für browserbasierte Angriffe in freier Wildbahn ausgenutzt.


Tools zum Testen und Ausnutzen

  • AddressSanitizer -- Laufzeit-Speicherfehlererkennung.

  • Valgrind -- Speicherfehler- und Leak-Erkennung.

  • AFL++ -- Fuzzing zum Auffinden von Pufferfehlern.

  • Coverity -- Statische Analyse für Pufferprobleme.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer." https://cwe.mitre.org/data/definitions/119.html

  2. CERT. "ARR30-C: Do not form or use out-of-bounds pointers or array subscripts." https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts