Verwendung von sizeof() auf einem Zeigertyp

Beschreibung

Verwendung von sizeof() auf einem Zeigertyp tritt auf, wenn ein Programmierer fälschlicherweise den sizeof-Operator auf eine Zeigervariable anwendet anstatt auf die Daten, auf die sie zeigt, oder das beabsichtigte Array. Dies passiert typischerweise, wenn Arrays zu Zeigern zerfallen, wenn sie an Funktionen übergeben werden, oder wenn Programmierer Zeigergröße mit Datengröße verwechseln. Auf 64-Bit-Systemen gibt sizeof(pointer) 8 zurück, während sizeof(struct) Hunderte von Bytes zurückgeben könnte—die Verwendung der falschen Größe führt zu Puffer-Unterallokation, unvollständigen Kopien und Speicherkorruption.

Risiko

Falsche sizeof-Verwendung verursacht Pufferüberläufe, Informationspreisgabe und Speicherkorruption. Wenn Speicher mit sizeof auf einem Pointer (typischerweise 4 oder 8 Bytes) anstatt der Strukturgröße allokiert wird, überlaufen nachfolgende Schreibvorgänge den unterdimensionierten Puffer. Speicherkopieroperationen mit Pointer-sizeof kopieren nur teilweise Daten, was möglicherweise Informationen preisgibt oder korrumpiert. Diese Schwäche ist besonders gefährlich, weil Code korrekt erscheinen und sogar in einigen Fällen funktionieren kann, bevor katastrophales Versagen auftritt.

Lösung

Wenden Sie sizeof immer auf den dereferenzierten Pointer oder den tatsächlichen Typ an, nicht auf den Pointer selbst: Verwenden Sie sizeof(*ptr) anstelle von sizeof(ptr). Beim Allokieren von Arrays verwenden Sie sizeof(element) * count. Verwenden Sie statische Analysewerkzeuge, die sizeof-Missbrauch erkennen. In modernem C++ bevorzugen Sie std::array oder std::vector, die ihre eigene Größe verfolgen. Definieren Sie Allokationsmakros oder -Templates, die die Größe korrekt berechnen. Code-Review sollte sizeof-Argumente spezifisch prüfen.

Häufige Auswirkungen

AuswirkungDetails
IntegritätUmfang: Buffer Overflow

Unterdimensionierte Allokationen führen zu Heap-Pufferüberläufen.
VerfügbarkeitUmfang: Speicherkorruption

Falsche Kopien korrumpieren benachbarte Speicherstrukturen.
VertraulichkeitUmfang: Informationspreisgabe

Teilweise Kopien können nicht initialisierten oder benachbarten Speicher offenlegen.

Beispielcode

Anfälliger Code

// ANFÄLLIG: sizeof Pointer anstelle von Struktur
struct UserRecord {
    char name[64];
    char email[128];
    int privileges;
    time_t last_login;
};

struct UserRecord* create_user_vulnerable(const char* name) {
    // Falsch! sizeof(struct UserRecord*) ist 8 Bytes auf 64-Bit
    // Aber struct UserRecord ist ~208 Bytes!
    struct UserRecord* user = malloc(sizeof(user));

    // Buffer Overflow beim Schreiben in unterdimensionierte Allokation!
    strcpy(user->name, name);  // Heap-Korruption!

    return user;
}

// ANFÄLLIG: Array zerfällt zu Pointer
void process_array_vulnerable(int arr[]) {
    // Falsch! arr ist hier tatsächlich ein Pointer
    // sizeof(arr) ist sizeof(int*), nicht Array-Größe!
    int count = sizeof(arr) / sizeof(arr[0]);

    printf("Verarbeite %d Elemente\n", count);  // Falsche Anzahl!

    for (int i = 0; i < count; i++) {
        process(arr[i]);  // Verarbeitet nur 1-2 Elemente!
    }
}

// ANFÄLLIG: memcpy mit Pointer-sizeof
void copy_record_vulnerable(struct UserRecord* dest, struct UserRecord* src) {
    // Falsch! Kopiert nur 8 Bytes anstelle der vollständigen Struktur
    memcpy(dest, src, sizeof(src));

    // Größter Teil der Struktur nicht kopiert!
}

// ANFÄLLIG: Allokierung von Array von Strukturen
struct UserRecord* create_users_vulnerable(int count) {
    // Falsch! Allokiert count * 8 Bytes anstelle von count * 208 Bytes
    struct UserRecord* users = malloc(count * sizeof(users));

    return users;  // Massiv unterdimensionierter Puffer!
}

// ANFÄLLIG: String-Puffer mit Pointer-sizeof
char* duplicate_string_vulnerable(const char* str) {
    // Falsch! Allokiert nur 8 Bytes unabhängig von String-Länge
    char* copy = malloc(sizeof(str));

    strcpy(copy, str);  // Buffer Overflow!

    return copy;
}

// ANFÄLLIG: memset mit falscher Größe
void clear_record_vulnerable(struct UserRecord* record) {
    // Falsch! Löscht nur erste 8 Bytes
    memset(record, 0, sizeof(record));

    // Größter Teil der Struktur enthält noch alte Daten!
}

// ANFÄLLIG: Array-Parameter in Funktion
void init_buffer_vulnerable(char buffer[1024]) {
    // Falsch! buffer ist ein Pointer, sizeof gibt 8 zurück
    memset(buffer, 0, sizeof(buffer));

    // Nur erste 8 Bytes genullt, Rest nicht initialisiert!
}

// ANFÄLLIG: Flexible Array Member Fehlberechnung
struct Message {
    int type;
    int length;
    char data[];  // Flexible Array Member
};

struct Message* create_message_vulnerable(const char* content, int len) {
    // Falsch! Berücksichtigt flexibles Array nicht
    struct Message* msg = malloc(sizeof(msg) + len);

    msg->length = len;
    memcpy(msg->data, content, len);  // Schreibt über Allokation hinaus!

    return msg;
}
// ANFÄLLIG: C++ mit denselben Problemen
class Record {
    std::string name;
    std::string data;
    int value;
};

// ANFÄLLIG: Verwendung von sizeof auf Pointer
Record* createRecordVulnerable() {
    // sizeof(Record*) ist 8, sizeof(Record) ist viel größer
    Record* rec = (Record*)malloc(sizeof(rec));

    // Objekt nicht ordnungsgemäß konstruiert!
    new (rec) Record();  // Placement new auf unterdimensioniertem Puffer!

    return rec;
}

// ANFÄLLIG: Template mit Pointer
template<typename T>
T* allocate_vulnerable(T* example) {
    // Falsch! sizeof(example) ist Zeigergröße
    return (T*)malloc(sizeof(example));
}

// ANFÄLLIG: Array-Größenerkennung in Template
template<typename T>
void processArray_vulnerable(T* arr) {
    // Falsch! Kann Array-Größe nicht von Pointer bestimmen
    constexpr size_t count = sizeof(arr) / sizeof(T);  // Immer 1

    for (size_t i = 0; i < count; i++) {
        process(arr[i]);
    }
}

// ANFÄLLIG: Smart-Pointer-Missbrauch
void processSmartPointer_vulnerable() {
    auto ptr = std::make_unique<Record[]>(10);

    // Falsch! sizeof(ptr) ist Größe des unique_ptr-Objekts
    // Nicht das Array das es verwaltet!
    size_t size = sizeof(ptr);
}
// ANFÄLLIG: Doppelzeiger-Verwirrung
struct Node {
    int value;
    struct Node* next;
};

void allocate_nodes_vulnerable(struct Node** nodes, int count) {
    // Erste Allokation - korrekt für Array von Zeigern
    *nodes = malloc(count * sizeof(struct Node*));

    for (int i = 0; i < count; i++) {
        // Falsch! Allokiert sizeof(struct Node**) nicht sizeof(struct Node)
        (*nodes)[i].next = malloc(sizeof(nodes));  // Viel zu klein!
    }
}

// ANFÄLLIG: Struktur enthält Pointer
struct Container {
    int count;
    int* values;  // Pointer auf Array
};

struct Container* create_container_vulnerable(int count) {
    struct Container* c = malloc(sizeof(struct Container));

    // Falsch! sizeof(c->values) ist Zeigergröße
    // Sollte count * sizeof(int) sein
    c->values = malloc(sizeof(c->values));
    c->count = count;

    return c;
}

Korrigierter Code

// SICHER: sizeof auf dereferenzierten Pointer
struct UserRecord* create_user_safe(const char* name) {
    // Korrekt! sizeof(*user) ist sizeof(struct UserRecord)
    struct UserRecord* user = malloc(sizeof(*user));

    if (user == NULL) return NULL;

    strncpy(user->name, name, sizeof(user->name) - 1);
    user->name[sizeof(user->name) - 1] = '\0';

    return user;
}

// SICHER: Array-Größe explizit übergeben
void process_array_safe(int* arr, size_t count) {
    printf("Verarbeite %zu Elemente\n", count);

    for (size_t i = 0; i < count; i++) {
        process(arr[i]);
    }
}

// Makro um Array-Größe zu ermitteln (funktioniert nur bei echten Arrays, nicht Zeigern)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

// Sichere Verwendung mit echtem Array
void caller_safe() {
    int numbers[100];
    process_array_safe(numbers, ARRAY_SIZE(numbers));
}

// SICHER: memcpy mit Struktur-sizeof
void copy_record_safe(struct UserRecord* dest, struct UserRecord* src) {
    // Korrekt! sizeof(*src) ist Strukturgröße
    memcpy(dest, src, sizeof(*src));
}

// Oder den Typnamen verwenden
void copy_record_safe_v2(struct UserRecord* dest, struct UserRecord* src) {
    memcpy(dest, src, sizeof(struct UserRecord));
}

// SICHER: Array-Allokation
struct UserRecord* create_users_safe(int count) {
    // Korrekt! sizeof(*users) ist Strukturgröße
    struct UserRecord* users = malloc(count * sizeof(*users));

    return users;
}

// SICHER: String-Duplizierung
char* duplicate_string_safe(const char* str) {
    size_t len = strlen(str) + 1;
    char* copy = malloc(len);

    if (copy == NULL) return NULL;

    memcpy(copy, str, len);
    return copy;
}

// Oder strdup verwenden wenn verfügbar
char* duplicate_string_safe_v2(const char* str) {
    return strdup(str);
}

// SICHER: memset mit korrekter Größe
void clear_record_safe(struct UserRecord* record) {
    // Korrekt! sizeof(*record) ist Strukturgröße
    memset(record, 0, sizeof(*record));
}

// SICHER: Puffer mit expliziter Größe
void init_buffer_safe(char* buffer, size_t buffer_size) {
    memset(buffer, 0, buffer_size);
}

// Aufrufer liefert Größe
void caller_buffer_safe() {
    char buffer[1024];
    init_buffer_safe(buffer, sizeof(buffer));
}

// SICHER: Flexible Array Member
struct Message* create_message_safe(const char* content, int len) {
    // Korrekt! sizeof(struct Message) plus Array-Größe
    struct Message* msg = malloc(sizeof(struct Message) + len);

    if (msg == NULL) return NULL;

    msg->type = 0;
    msg->length = len;
    memcpy(msg->data, content, len);

    return msg;
}

// SICHER: Typsicheres Allokationsmakro
#define ALLOC(type) ((type*)malloc(sizeof(type)))
#define ALLOC_ARRAY(type, count) ((type*)malloc(sizeof(type) * (count)))

// Verwendung
void use_macros() {
    struct UserRecord* user = ALLOC(struct UserRecord);
    struct UserRecord* users = ALLOC_ARRAY(struct UserRecord, 10);
}
// SICHER: C++ mit ordnungsgemäßer Größenbestimmung

// SICHER: new anstelle von malloc verwenden
Record* createRecordSafe() {
    return new Record();  // Automatisch korrekte Größe
}

// SICHER: sizeof auf Typ oder dereferenzierten Pointer verwenden
Record* createRecordManualSafe() {
    Record* rec = (Record*)malloc(sizeof(Record));
    // Oder: malloc(sizeof(*rec))

    if (rec) {
        new (rec) Record();
    }
    return rec;
}

// SICHER: Template mit ordnungsgemäßer Größenbestimmung
template<typename T>
T* allocate_safe() {
    return (T*)malloc(sizeof(T));
}

// SICHER: Array mit expliziter Anzahl
template<typename T>
T* allocate_array_safe(size_t count) {
    return new T[count];
}

// SICHER: std::array für Kompilierzeit-Größe verwenden
template<typename T, size_t N>
void processArray_safe(std::array<T, N>& arr) {
    for (size_t i = 0; i < N; i++) {
        process(arr[i]);
    }
}

// SICHER: std::vector für Laufzeit-Größe verwenden
template<typename T>
void processVector_safe(std::vector<T>& vec) {
    for (size_t i = 0; i < vec.size(); i++) {
        process(vec[i]);
    }
}

// SICHER: Template Array-Größenableitung
template<typename T, size_t N>
constexpr size_t array_size(T (&)[N]) {
    return N;
}

void use_array_size() {
    int numbers[100];
    size_t count = array_size(numbers);  // Gibt 100 zurück
}

// SICHER: Smart-Pointer mit ordnungsgemäßer Allokation
void processSmartPointer_safe() {
    auto ptr = std::make_unique<Record[]>(10);

    // Größe separat verfolgen oder vector verwenden
    size_t size = 10;

    // Besser: vector verwenden
    std::vector<Record> records(10);
    size_t actualSize = records.size();
}
// SICHER: Doppelzeiger mit korrekten Größen
void allocate_nodes_safe(struct Node** nodes, int count) {
    // Array von Node-Zeigern (wenn das die Absicht ist)
    *nodes = malloc(count * sizeof(struct Node));

    // Jeden Knoten initialisieren
    for (int i = 0; i < count; i++) {
        (*nodes)[i].value = 0;
        (*nodes)[i].next = NULL;
    }
}

// SICHER: Struktur mit Pointer - separat allokieren
struct Container* create_container_safe(int count) {
    struct Container* c = malloc(sizeof(*c));
    if (c == NULL) return NULL;

    // Korrekt! count Integers allokieren
    c->values = malloc(count * sizeof(*c->values));
    if (c->values == NULL) {
        free(c);
        return NULL;
    }

    c->count = count;
    return c;
}

// SICHER: sizeof konsistent verwenden
typedef struct {
    int id;
    char name[50];
    double balance;
} Account;

Account* clone_account(const Account* src) {
    // Alle diese sind äquivalent und korrekt:
    Account* copy = malloc(sizeof(Account));
    // Account* copy = malloc(sizeof(*copy));
    // Account* copy = malloc(sizeof *copy);  // Klammern optional für Variablen

    if (copy) {
        memcpy(copy, src, sizeof(*copy));
    }

    return copy;
}

Ausgenutzt in der Praxis

OpenSSL Pufferüberläufe

Historische OpenSSL-Schwachstellen haben falsche sizeof-Verwendung beinhaltet, die zu Pufferüberläufen bei kryptografischen Operationen führten.

Linux-Kernel Heap-Überläufe

Linux-Kernel-CVEs resultierten aus sizeof-Missbrauch beim Allokieren von Kernel-Strukturen, was Privilegieneskalation ermöglichte.

Embedded-Systems-Korruption

Ressourcenbeschränkte Embedded-Systeme haben Speicherkorruption durch sizeof-Fehler erfahren, bei denen begrenzte Tests die Bugs nicht aufdeckten.


Werkzeuge zum Testen/Ausnutzen


CVE-Beispiele

  • CVE-2017-9445 — systemd-resolved Heap-Buffer Overflow.

  • CVE-2016-7117 — Linux-Kernel recvmmsg sizeof-Problem.

  • Mehrere Buffer Overflow-CVEs auf sizeof-Missbrauch zurückzuführen.


Referenzen

  1. MITRE. "CWE-467: Use of sizeof() on a Pointer Type." https://cwe.mitre.org/data/definitions/467.html

  2. CERT C. "ARR01-C: Do not apply the sizeof operator to a pointer when taking the size of an array." https://wiki.sei.cmu.edu/confluence/display/c/