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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Informationspreisgabe Out-of-Bounds-Leseoperationen können sensible Daten aus angrenzenden Speicherbereichen preisgeben. |
| Integrität | Bereich: Code-Ausführung Out-of-Bounds-Schreiboperationen können Code-Pointer beschädigen und beliebige Code-Ausführung ermöglichen. |
| Verfügbarkeit | Bereich: 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
-
CVE-2014-0160 -- Heartbleed Buffer-Over-Read.
-
CVE-2017-0144 -- EternalBlue SMB Buffer Overflow.
-
CVE-2021-44228 -- Log4Shell (verwandte Speicherprobleme).
Referenzen
-
MITRE. "CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer." https://cwe.mitre.org/data/definitions/119.html
-
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