Unsachgemäße Aktualisierung des Referenzzählers

Beschreibung

Unsachgemäße Aktualisierung des Referenzzählers tritt auf, wenn Software Referenzzählung zur Verwaltung von Ressourcen verwendet, aber den Referenzzähler nicht korrekt aktualisiert. Referenzzählung ist eine Speicherverwaltungstechnik, bei der jede Ressource einen zugehörigen Zähler hat, der angibt, wie viele Referenzen auf sie zeigen. Wenn der Zähler Null erreicht, sollte die Ressource freigegeben werden. Wenn Referenzzähler falsch inkrementiert, dekrementiert oder nicht aktualisiert werden, wenn Referenzen erstellt oder zerstört werden, können Ressourcen vorzeitig freigegeben werden (Use-after-free) oder nie freigegeben werden (Speicherleck).

Risiko

Falsche Referenzzählung führt zu zwei primären Sicherheitsproblemen. Wenn Zähler zu schnell dekrementieren oder zu langsam inkrementieren, können Ressourcen freigegeben werden, während sie noch in Verwendung sind, was Use-after-free-Schwachstellen verursacht, die zu Codeausführung oder Abstürzen führen können. Wenn Zähler zu schnell inkrementieren oder zu langsam dekrementieren, werden Ressourcen nie freigegeben, was Speicherlecks verursacht, die durch Ressourcenerschöpfung zu Denial-of-Service führen. Race Conditions bei Referenzzähler-Aktualisierungen können beide Probleme gleichzeitig in Multithread-Anwendungen verursachen. Referenzzahlungsfehler sind besonders gefährlich, weil sie ausgenutzt werden können, um Speicher zu korrumpieren und beliebigen Code auszuführen.

Lösung

Auditieren Sie sorgfältig alle Codepfade, die Referenzen erstellen oder zerstoren, um sicherzustellen, dass Zähler korrekt aktualisiert werden. Verwenden Sie atomare Operationen für Referenzzähler-Aktualisierungen in Multithread-Code. Erwägen Sie die Verwendung von Smart Pointern oder automatischer Referenzzählung (ARC), wo verfügbar. Implementieren Sie Referenzzählung als wiederverwendbares Muster, um Reimplementierungsfehler zu vermeiden. Fügen Sie Assertions und Debugging-Hilfen hinzu, um Referenzzähler-Anomalien zu erkennen. Verwenden Sie statische Analysetools, um die Korrektheit von Referenzzählern zu verifizieren. Implementieren Sie defensive Prüfungen, die unmögliche Referenzzähler-Zustände erkennen. Erwägen Sie Alternativen zur manuellen Referenzzählung, wie Garbage Collection oder eigentümerschaftsbasierte Speicherverwaltung.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Falsche Zähler, die die Ressourcenfreigabe verhindern, verursachen Speicherlecks und Ressourcenerschöpfung.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz/Beenden/Neustart - Vorzeitige Ressourcenfreigabe verursacht Abstürze, wenn freigegebene Ressourcen zugegriffen werden.
Integrität, VertraulichkeitBereich: Integrität, Vertraulichkeit

Unbefugten Code ausführen - Use-after-free durch vorzeitige Freigabe kann beliebige Codeausführung ermöglichen.

Beispielcode + Lösungscode

Verwundbarer Code

// Verwundbar: Fehlendes Inkrement beim Erstellen einer neuen Referenz
typedef struct {
    int refcount;
    char *data;
    size_t size;
} Buffer;

Buffer* create_buffer(size_t size) {
    Buffer *buf = malloc(sizeof(Buffer));
    buf->data = malloc(size);
    buf->size = size;
    buf->refcount = 1;
    return buf;
}

void release_buffer(Buffer *buf) {
    buf->refcount--;
    if (buf->refcount == 0) {
        free(buf->data);
        free(buf);
    }
}

Buffer* vulnerable_copy_reference(Buffer *buf) {
    // Verwundbar: Fehlendes refcount-Inkrement!
    return buf;  // Neue Referenz erstellt ohne Zähler zu erhöhen
}

void vulnerable_usage() {
    Buffer *buf1 = create_buffer(100);  // refcount = 1
    Buffer *buf2 = vulnerable_copy_reference(buf1);  // refcount immer noch 1!

    release_buffer(buf1);  // refcount = 0, Buffer freigegeben

    // buf2 zeigt jetzt auf freigegebenen Speicher!
    memcpy(buf2->data, "absturz", 7);  // Use-after-free
}
// Verwundbar: Race Condition bei Referenzzählung
typedef struct {
    int refcount;  // Nicht atomar!
    void *resource;
} RefCounted;

void vulnerable_acquire(RefCounted *obj) {
    // Verwundbar: Nicht-atomares Inkrement
    // Thread 1: liest refcount (1)
    // Thread 2: liest refcount (1)
    // Thread 1: schreibt refcount (2)
    // Thread 2: schreibt refcount (2) - sollte 3 sein!
    obj->refcount++;
}

void vulnerable_release(RefCounted *obj) {
    // Verwundbar: Race zwischen Dekrement und Prüfung
    obj->refcount--;  // Thread 1: dekrementiert zu 0
                      // Thread 2: dekrementiert zu -1 (oder liest 0)

    if (obj->refcount == 0) {  // Beide Threads können 0 sehen
        free(obj->resource);    // Double free!
        free(obj);
    }
}

Sicherer Code

// Behoben: Ordnungsgemäße Referenzzählung mit explizitem Acquire
typedef struct {
    int refcount;
    char *data;
    size_t size;
} Buffer;

Buffer* create_buffer(size_t size) {
    Buffer *buf = malloc(sizeof(Buffer));
    if (!buf) return NULL;

    buf->data = malloc(size);
    if (!buf->data) {
        free(buf);
        return NULL;
    }

    buf->size = size;
    buf->refcount = 1;
    return buf;
}

Buffer* acquire_buffer(Buffer *buf) {
    if (buf) {
        // Behoben: Immer inkrementieren beim Erstellen neuer Referenz
        buf->refcount++;
    }
    return buf;
}

void release_buffer(Buffer *buf) {
    if (!buf) return;

    buf->refcount--;
    if (buf->refcount == 0) {
        free(buf->data);
        free(buf);
    }
}

void fixed_usage() {
    Buffer *buf1 = create_buffer(100);  // refcount = 1
    Buffer *buf2 = acquire_buffer(buf1);  // refcount = 2

    release_buffer(buf1);  // refcount = 1, Buffer noch gültig

    memcpy(buf2->data, "sicher", 6);  // Sicher!

    release_buffer(buf2);  // refcount = 0, Buffer freigegeben
}
// Behoben: Thread-sichere Referenzzählung mit Atomics
#include <stdatomic.h>

typedef struct {
    atomic_int refcount;
    void *resource;
} RefCounted;

RefCounted* create_refcounted(void *resource) {
    RefCounted *obj = malloc(sizeof(RefCounted));
    atomic_init(&obj->refcount, 1);
    obj->resource = resource;
    return obj;
}

void fixed_acquire(RefCounted *obj) {
    // Behoben: Atomares Inkrement
    atomic_fetch_add(&obj->refcount, 1);
}

void fixed_release(RefCounted *obj) {
    // Behoben: Atomares Dekrement und Prüfung
    if (atomic_fetch_sub(&obj->refcount, 1) == 1) {
        // Wir haben von 1 auf 0 dekrementiert - sicher zum Freigeben
        free(obj->resource);
        free(obj);
    }
}

CVE-Beispiele

  • CVE-2007-1383: Integer-Überlauf im Referenzzähler ermöglichte doppelte Zerstörung des Objekts.
  • CVE-2009-1709: Unsachgemäße Referenzzählung verursachte Use-after-free während Garbage Collection.
  • CVE-2011-0695: Race Condition beim Referenzzähler-Dekrement führte zu vorzeitiger Objektzerstörung.

Verwandte CWEs

  • CWE-664: Unsachgemäße Kontrolle einer Ressource durch ihre Lebensdauer (Eltern)
  • CWE-672: Operation auf einer Ressource nach Ablauf oder Freigabe (kann folgen)
  • CWE-772: Fehlende Freigabe von Ressource nach effektiver Lebensdauer (kann folgen)
  • CWE-416: Use After Free (kann folgen)

Referenzen

  1. MITRE Corporation. "CWE-911: Improper Update of Reference Count." https://cwe.mitre.org/data/definitions/911.html
  2. CERT C Secure Coding Standard. "MEM30-C. Do not access freed memory."
  3. Apple Developer Documentation. "Transitioning to ARC Release Notes."