Integer-Überlauf zu Buffer Overflow

Beschreibung

Integer-Überlauf zu Buffer Overflow ist eine zusammengesetzte Schwachstelle, bei der ein Integer-Überlauf während der Berechnung der Speicherallokationsgröße dazu führt, dass ein zu kleiner Puffer allokiert wird, was anschließend zu einem Buffer Overflow führt, wenn das Programm versucht, den unterdimensionierten Puffer zu verwenden. Dies tritt typischerweise bei der Berechnung der Größe für Speicherallokation auf — beispielsweise beim Multiplizieren einer Anzahl mit einer Elementgröße. Wenn die Berechnung überläuft, wickelt das Ergebnis zu einem kleinen Wert um, sodass malloc() oder ähnliche Funktionen einen winzigen Puffer allokieren.

Risiko

Integer-Überlauf-zu-Buffer Overflow-Schwachstellen sind hochgefährlich und werden häufig ausgenutzt. Angreifer können große Eingabewerte bereitstellen, die darauf ausgelegt sind, den Überlauf auszulösen, und so die Kontrolle über Speicherallokationsgrößen erlangen. Der nachfolgende Buffer Overflow ermöglicht Heap-Korruption, die für beliebige Codeausführung genutzt werden kann. Diese Schwachstellen umgehen oft andere Sicherheitsprüfungen, da die berechnete Größe die Validierung passiert, bevor der Überlauf auftritt.

Lösung

Validieren Sie alle Eingaben, die zu Speicherallokationsberechnungen beitragen, bevor Sie Arithmetik durchführen. Prüfen Sie vor der Multiplikation auf potenziellen Überlauf: wenn (a > SIZE_MAX / b), dann würde ein Überlauf auftreten. Verwenden Sie sichere Integer-Arithmetik-Bibliotheken, die Überlauf erkennen. Erwägen Sie die Verwendung von calloc() anstelle von malloc() mit Multiplikation, da einige Implementierungen auf Überlauf prüfen. Nutzen Sie Compiler-Optionen wie -ftrapv (GCC), um bei signiertem Überlauf abzufangen.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Datenmodifikation - Buffer Overflow korrumpiert Heap- oder Stack-Daten und modifiziert möglicherweise sicherheitskritische Informationen.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz - Speicherkorruption führt zu Abstürzen und Anwendungsinstabilität.
VertraulichkeitBereich: Vertraulichkeit, Integrität, Verfügbarkeit

Codeausführung - Heap-basierte Pufferüberläufe können für beliebige Codeausführung ausgenutzt werden.

Beispielcode und Lösung

Verwundbarer Code

// Verwundbar: Integer-Überlauf in Allokationsgrößenberechnung
#include <stdlib.h>
#include <string.h>

typedef struct {
    int width;
    int height;
    unsigned char* pixels;
} Image;

// Verwundbar: Integer-Überlauf vor malloc
Image* verwundbar_erstelle_bild(int width, int height) {
    Image* img = malloc(sizeof(Image));
    if (!img) return NULL;

    img->width = width;
    img->height = height;

    // VERWUNDBAR: width * height * 4 kann überlaufen
    // Wenn width=65536, height=65536: 65536 * 65536 * 4 überläuft 32-Bit
    // Ergebnis könnte eine kleine Zahl wie 0 oder 4 sein
    size_t size = width * height * 4;  // Integer-Überlauf!

    img->pixels = malloc(size);  // Allokiert winzigen Puffer

    // Späterer Code schreibt vollständige Bilddaten, verursacht Buffer Overflow
    return img;
}

// Verwundbar: Array-Allokation mit Überlauf
typedef struct {
    char name[32];
    int id;
} Record;

Record* verwundbar_lade_records(int num_records) {
    // VERWUNDBAR: num_records * sizeof(Record) kann überlaufen
    // sizeof(Record) = 36, wenn num_records = 119304648
    // 119304648 * 36 = 4.294.967.328 was auf 32-Bit zu kleinem Wert umwickelt
    Record* records = malloc(num_records * sizeof(Record));

    if (records == NULL) {
        return NULL;
    }

    // Später: memcpy oder Schleife schreibt zu allen num_records Einträgen
    // verursacht massiven Buffer Overflow
    for (int i = 0; i < num_records; i++) {
        read_record(&records[i]);  // Schreibt über allozierten Speicher hinaus
    }

    return records;
}

// Verwundbar: String-Verkettung Längenberechnung
char* verwundbar_concat(const char* str1, const char* str2) {
    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);

    // Verwundbar: len1 + len2 + 1 kann überlaufen wenn Strings riesig
    size_t total = len1 + len2 + 1;  // Überlauf wickelt zu kleinem Wert um

    char* result = malloc(total);  // Winzige Allokation
    if (result) {
        strcpy(result, str1);
        strcat(result, str2);  // Buffer Overflow!
    }
    return result;
}

// Verwundbar: Paketheader spezifiziert Größe
void verwundbar_verarbeite_paket(unsigned char* packet) {
    // Angreifer kontrolliert Paketheader
    unsigned int element_count = *(unsigned int*)packet;
    unsigned int element_size = *(unsigned int*)(packet + 4);

    // Verwundbar: Multiplikationsüberlauf
    unsigned int total_size = element_count * element_size;  // Überlauf!

    char* buffer = malloc(total_size);  // Kleine Allokation

    // Paketdaten in Buffer kopieren - Überlauf
    memcpy(buffer, packet + 8, element_count * element_size);

    process_data(buffer);
    free(buffer);
}

Sichere Lösung

// Sicher: Sichere Integer-Arithmetik mit Überlaufprüfung
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <limits.h>

// Sichere Multiplikation mit Überlaufprüfung
int sichere_multiplikation(size_t a, size_t b, size_t* result) {
    if (a > 0 && b > SIZE_MAX / a) {
        return -1;  // Würde überlaufen
    }
    *result = a * b;
    return 0;
}

// Sichere Addition mit Überlaufprüfung
int sichere_addition(size_t a, size_t b, size_t* result) {
    if (a > SIZE_MAX - b) {
        return -1;  // Würde überlaufen
    }
    *result = a + b;
    return 0;
}

// Sicher: Bilderstellung mit Überlaufschutz
#define MAX_IMAGE_SIZE (1024 * 1024 * 100)  // 100MB max

Image* sichere_erstelle_bild(int width, int height) {
    // Sicher: Eingaben validieren
    if (width <= 0 || height <= 0) {
        return NULL;
    }

    // Sicher: Vor Allokation auf Überlauf prüfen
    size_t pixel_count;
    if (sichere_multiplikation((size_t)width, (size_t)height, &pixel_count) != 0) {
        return NULL;  // Überlauf würde auftreten
    }

    size_t size;
    if (sichere_multiplikation(pixel_count, 4, &size) != 0) {
        return NULL;  // Überlauf würde auftreten
    }

    // Zusätzliche Plausibilitätsprüfung
    if (size > MAX_IMAGE_SIZE) {
        return NULL;  // Unvernünftig groß
    }

    Image* img = malloc(sizeof(Image));
    if (!img) return NULL;

    img->width = width;
    img->height = height;
    img->pixels = malloc(size);

    if (!img->pixels) {
        free(img);
        return NULL;
    }

    return img;
}

// Sicher: Verwendung von calloc, das möglicherweise auf Überlauf prüft
#define MAX_RECORDS 1000000

Record* sichere_lade_records(size_t num_records) {
    // Sicher: Anzahl validieren
    if (num_records == 0 || num_records > MAX_RECORDS) {
        return NULL;
    }

    // Sicher: calloc() mit separater Anzahl und Größe
    // Viele Implementierungen prüfen intern auf Überlauf
    Record* records = calloc(num_records, sizeof(Record));

    if (records == NULL) {
        return NULL;
    }

    for (size_t i = 0; i < num_records; i++) {
        if (read_record(&records[i]) != 0) {
            free(records);
            return NULL;
        }
    }

    return records;
}

// Sicher: String-Verkettung mit Überlaufprüfung
char* sichere_concat(const char* str1, const char* str2) {
    if (str1 == NULL || str2 == NULL) {
        return NULL;
    }

    size_t len1 = strlen(str1);
    size_t len2 = strlen(str2);

    // Sicher: Auf Überlauf prüfen
    size_t total;
    if (sichere_addition(len1, len2, &total) != 0) {
        return NULL;
    }
    if (sichere_addition(total, 1, &total) != 0) {
        return NULL;
    }

    char* result = malloc(total);
    if (result) {
        memcpy(result, str1, len1);
        memcpy(result + len1, str2, len2 + 1);  // Null-Terminator einschließen
    }
    return result;
}

// Sicher: Paketverarbeitung mit sicherer Arithmetik
#define MAX_PACKET_SIZE (1024 * 1024)  // 1MB max

int sichere_verarbeite_paket(unsigned char* packet, size_t packet_len) {
    if (packet_len < 8) {
        return -1;  // Paket zu klein für Header
    }

    unsigned int element_count = *(unsigned int*)packet;
    unsigned int element_size = *(unsigned int*)(packet + 4);

    // Sicher: Vor Multiplikation auf Überlauf prüfen
    size_t total_size;
    if (sichere_multiplikation((size_t)element_count, (size_t)element_size,
                      &total_size) != 0) {
        return -1;
    }

    // Sicher: Plausibilitätsprüfung der Größe
    if (total_size > MAX_PACKET_SIZE) {
        return -1;
    }

    // Sicher: Verifizieren, dass Paket genug Daten enthält
    if (packet_len < 8 + total_size) {
        return -1;
    }

    char* buffer = malloc(total_size);
    if (buffer == NULL) {
        return -1;
    }

    memcpy(buffer, packet + 8, total_size);
    process_data(buffer);
    free(buffer);

    return 0;
}

// Moderner C11-Ansatz mit geprüfter Arithmetik (falls verfügbar)
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#include <stdckdint.h>  // C23 geprüfte Integer-Arithmetik

Image* moderne_erstelle_bild(int width, int height) {
    if (width <= 0 || height <= 0) return NULL;

    size_t pixel_count, size;

    // C23 geprüfte Multiplikation
    if (ckd_mul(&pixel_count, (size_t)width, (size_t)height)) {
        return NULL;  // Überlauf
    }
    if (ckd_mul(&size, pixel_count, 4)) {
        return NULL;  // Überlauf
    }

    Image* img = malloc(sizeof(Image));
    if (!img) return NULL;

    img->pixels = malloc(size);
    if (!img->pixels) {
        free(img);
        return NULL;
    }

    img->width = width;
    img->height = height;
    return img;
}
#endif

Ausgenutzt in der Praxis

libpng Integer-Überlauf (2004)

Ein Integer-Überlauf in der weit verbreiteten libpng-Bibliothek bei der Berechnung von Bildpuffergrößen ermöglichte Remote-Code-Ausführung durch speziell gestaltete PNG-Bilder. Dies betraf zahlreiche Browser und Bildverarbeitungsanwendungen.

Microsoft GDI+ Integer-Überlauf (2004)

Ein kritischer Integer-Überlauf in der Windows GDI+-Bibliothek bei der JPEG-Verarbeitung ermöglichte Remote-Code-Ausführung durch das Öffnen manipulierter Bilder. Die Schwachstelle betraf Windows XP, Server 2003 und Office.

Android Stagefright (2015)

Integer-Überläufe in der Android-Medienbibliothek Stagefright ermöglichten Remote-Code-Ausführung durch speziell gestaltete MMS-Nachrichten. Etwa 950 Millionen Android-Geräte waren betroffen.


Tools zum Testen und Ausnutzen


CVE-Beispiele

  • CVE-2021-43537 — Typumwandlungsfehler verursacht Integer-Überlauf zu Heap-Buffer Overflow.

  • CVE-2017-1000121 — Ungeprüfte benutzergesteuerte Metadatenwerte führen zu Integer-Überlauf.

  • CVE-2015-3864 — Android Stagefright Integer-Überlauf.


Referenzen

  1. MITRE Corporation. "CWE-680: Integer Overflow to Buffer Overflow." https://cwe.mitre.org/data/definitions/680.html

  2. CERT C Coding Standard. "INT30-C: Ensure that unsigned integer operations do not wrap." https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap

  3. CERT C Coding Standard. "INT32-C: Ensure that operations on signed integers do not result in overflow." https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow