Vorzeitige Freigabe einer Ressource während der erwarteten Lebensdauer

Beschreibung

Vorzeitige Freigabe einer Ressource während der erwarteten Lebensdauer ist eine Ressourcenverwaltungs-Schwachstelle, bei der Software eine Ressource freigibt, während sie noch von der Software selbst oder einem anderen Akteur verwendet werden soll. Im Gegensatz zu CWE-825, das sich mit der Verwendung bereits freigegebener Ressourcen befasst, beschreibt diese Schwachstelle den Akt der zu frühen Freigabe einer Ressource - bevor ihre erwartete Lebensdauer geendet hat. Wenn eine Ressource vorzeitig freigegeben wird, können nachfolgende Operationen auf dieser Ressource stattfinden, nachdem sie zweckentfremdet wurde, was Bedingungen ähnlich wie bei Use-after-free-Schwachstellen erzeugt. Dies kann zu Denial of Service, Informationsoffenlegung oder Codeausführung führen.

Risiko

Vorzeitige Ressourcenfreigabe erzeugt Race Conditions und Zustandsverwirrung. Wenn eine Ressource vor der erwarteten Zeit freigegeben wird, wird anderer Code, der legitimerweise Referenzen auf diese Ressource hält, auf ungültigem oder zweckentfremdetem Speicher/Handles operieren. Dies kann sensible Daten preisgeben, wenn die neu allokierte Ressource Daten eines anderen Benutzers enthält. Abstürze treten auf, wenn auf freigegebene Ressourcen zugegriffen wird, was Denial of Service verursacht. In speicherbezogenen Fällen können Angreifer potenziell den Inhalt des neu allokierten Speichers kontrollieren, um Codeausführung zu erreichen. Die Schwachstelle manifestiert sich oft in komplexem Multithread-Code oder wenn das Ressourcen-Lebensdauer-Management unklar ist.

Lösung

Implementieren Sie klare Eigentümerschaftssemantik für alle Ressourcen. Verwenden Sie Referenzzählung, wenn mehrere Komponenten Zugriff auf dieselbe Ressource benötigen. Verwenden Sie in C++ Smart Pointer (shared_ptr für geteilte Eigentümerschaft, unique_ptr für exklusive Eigentümerschaft), um Lebensdauern automatisch zu verwalten. Stellen Sie sicher, dass Ressourcen nur freigegeben werden, wenn alle Referenzen damit fertig sind. Dokumentieren Sie Ressourcen-Lebensdauer-Erwartungen klar in API-Verträgen. Verwenden Sie statische Analyse, um vorzeitige Freigaben zu erkennen. Verwenden Sie in Multithread-Code ordnungsgemäße Synchronisation, um Races zwischen Freigabe und Verwendung zu verhindern. Erwägen Sie die Verwendung von RAII-Mustern (Resource Acquisition Is Initialization), um die Ressourcenlebensdauer an den Objektbereich zu binden.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Vorzeitig freigegebene Ressourcen können neu allokiert werden und Daten aus verschiedenen Benutzer- oder Kontextquellen enthalten, was sensible Informationen preisgibt.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz, Beendigung oder Neustart - Freigegebene Ressourcen sind möglicherweise nicht in erwarteten Zuständen, wenn darauf zugegriffen wird, was Abstürze oder Fehler verursacht.
Integrität, Vertraulichkeit, VerfügbarkeitBereich: Integrität, Vertraulichkeit, Verfügbarkeit

Unerlaubten Code oder Befehle ausführen - Wenn die vorzeitige Freigabe Speicher betrifft, der bei Funktionsaufrufen oder Schreibvorgängen verwendet wird, wird Codeausführung möglich.

Beispielcode

Anfälliger Code

// Anfällig: Ressource freigegeben während anderer Thread sie verwenden könnte
#include <pthread.h>

char *shared_buffer = NULL;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void* writer_thread(void* arg) {
    pthread_mutex_lock(&lock);
    if (shared_buffer == NULL) {
        shared_buffer = malloc(256);
        strcpy(shared_buffer, "wichtige daten");
    }
    pthread_mutex_unlock(&lock);
    return NULL;
}

void* cleanup_thread(void* arg) {
    // Anfällig: Vorzeitige Freigabe - writer könnte noch nicht fertig sein
    // Keine Koordination mit writer_thread
    if (shared_buffer != NULL) {
        free(shared_buffer);
        shared_buffer = NULL;
    }
    return NULL;
}

void* reader_thread(void* arg) {
    // Kann auf Puffer nach vorzeitiger Freigabe zugreifen
    if (shared_buffer != NULL) {
        printf("Daten: %s\n", shared_buffer);  // Use after premature free
    }
    return NULL;
}
// Anfällig: Socket vorzeitig geschlossen
void vulnerable_connection_handler(int client_socket) {
    pthread_t thread;

    // Handler-Thread starten
    pthread_create(&thread, NULL, handle_client, (void*)(long)client_socket);

    // Anfällig: Haupt-Thread schließt Socket bevor Handler fertig ist
    close(client_socket);  // Handler könnte das noch brauchen!

    // Wartet nicht einmal auf Handler-Abschluss
}

Korrigierter Code

// Korrigiert: Referenzzählung für gemeinsame Ressourcen verwenden
#include <pthread.h>
#include <stdatomic.h>

typedef struct {
    char *data;
    atomic_int refcount;
    pthread_mutex_t lock;
} shared_resource_t;

shared_resource_t* create_resource(void) {
    shared_resource_t *res = malloc(sizeof(shared_resource_t));
    res->data = malloc(256);
    atomic_init(&res->refcount, 1);
    pthread_mutex_init(&res->lock, NULL);
    return res;
}

void acquire_resource(shared_resource_t *res) {
    atomic_fetch_add(&res->refcount, 1);
}

void release_resource(shared_resource_t *res) {
    if (atomic_fetch_sub(&res->refcount, 1) == 1) {
        // Korrigiert: Nur freigeben wenn letzte Referenz freigegeben wird
        pthread_mutex_destroy(&res->lock);
        free(res->data);
        free(res);
    }
}
// Korrigiert: Auf Handler warten bevor geschlossen wird
void fixed_connection_handler(int client_socket) {
    pthread_t thread;

    pthread_create(&thread, NULL, handle_client, (void*)(long)client_socket);

    // Korrigiert: Auf Handler-Abschluss warten
    pthread_join(thread, NULL);

    // Jetzt sicher zu schließen
    close(client_socket);
}

// Oder: Handler besitzt den Socket
void* handle_client_owns_socket(void* arg) {
    int socket = (int)(long)arg;

    // Handler erledigt seine Arbeit...
    process_client(socket);

    // Handler schließt Socket wenn fertig
    close(socket);
    return NULL;
}
// Korrigiert: shared_ptr für geteilte Eigentümerschaft verwenden
#include <memory>
#include <map>

class FixedCache {
    std::map<int, std::shared_ptr<Data>> cache;

public:
    std::shared_ptr<Data> get(int id) {
        return cache[id];  // Gibt shared_ptr zurück - Aufrufer bekommt Referenz
    }

    void evict(int id) {
        // Korrigiert: Entfernt nur aus Cache
        // Tatsächliche Daten gelöscht wenn alle shared_ptrs out of scope gehen
        cache.erase(id);
    }
};

// Verwendung:
// auto ptr = cache.get(5);  // ptr ist shared_ptr
// cache.evict(5);           // OK - ptr immer noch gültig
// ptr->use();               // Sicher - ptr hält Daten am Leben

CVE-Beispiele

  • CVE-2009-3547: Race Condition bei Pipe-Behandlung ermöglichte vorzeitige Freigabe von Ressourcen, was zu NULL-Pointer-Dereferenzierung führte.

Verwandte CWEs

  • CWE-666: Operation auf Ressource in falscher Phase der Lebensdauer (Eltern)
  • CWE-672: Operation auf einer Ressource nach Ablauf oder Freigabe (kann folgen)
  • CWE-825: Dereferenzierung eines abgelaufenen Zeigers (verwandt)
  • CWE-416: Use After Free (kann resultieren)
  • CWE-415: Double Free (kann resultieren)

Referenzen

  1. MITRE Corporation. "CWE-826: Premature Release of Resource During Expected Lifetime." https://cwe.mitre.org/data/definitions/826.html
  2. CERT C Secure Coding Standard. "MEM30-C. Do not access freed memory."
  3. C++ Core Guidelines. "R: Resource Management." https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#r-resource-management