Dereferenzierung eines abgelaufenen Zeigers
Beschreibung
Dereferenzierung eines abgelaufenen Zeigers ist eine Speichersicherheits-Schwachstelle, bei der Software einen Pointer dereferenziert, der auf einen Speicherort verweist, der zuvor gültig war, aber nicht mehr gültig ist. Dies tritt typischerweise auf, wenn Speicher freigegeben wird, aber ein Pointer auf diesen Ort (ein "dangling Pointer") beibehalten und später verwendet wird. Sobald Speicher freigegeben ist, kann er für einen anderen Zweck neu allokiert werden. Der Zugriff über den ursprünglichen Pointer kann dazu führen, dass das Programm Daten liest oder modifiziert, die einer anderen Funktion oder einem anderen Prozess gehören, was zu unvorhersehbarem Verhalten, Denial of Service, Informationsoffenlegung oder Codeausführung führt.
Risiko
Diese Schwachstelle ist extrem gefährlich, weil der freigegebene Speicher oft wiederverwendet wird. Angreifer können Eingaben erstellen, die bestimmte Speicherallokationsmuster verursachen, was ihnen ermöglicht zu kontrollieren, welche Daten den zuvor freigegebenen Speicherort belegen. Wenn der dangling Pointer später dereferenziert wird, greift er auf angreiferkontrollierte Daten zu. Wenn der Pointer für Funktionsaufrufe verwendet wurde, können Angreifer die Ausführung zu beliebigem Code umleiten. Use-after-free-Schwachstellen gehören zu den am häufigsten ausgenutzten Speicherbeschädigungs-Problemen in moderner Software. Das Timing und Reallokationsverhalten machen die Ausnutzung durch einfache Fixes schwer zu verhindern.
Lösung
Setzen Sie Pointer unmittelbar nach der Freigabe des Speichers, auf den sie verweisen, auf NULL, und prüfen Sie immer auf NULL vor der Dereferenzierung. Dies ist jedoch keine vollständige Lösung, wenn mehrere Pointer auf denselben Speicher verweisen. Verwenden Sie Smart Pointer in C++ (unique_ptr, shared_ptr), die automatisch Lebensdauern verwalten und dangling Referenzen verhindern. Bevorzugen Sie Sprachen mit automatischer Garbage Collection, wenn machbar. Verwenden Sie statische Analysetools, um potenzielle Use-after-free-Muster zu erkennen. Aktivieren Sie während der Entwicklung und des Testens AddressSanitizer, um abgelaufene Pointer-Dereferenzierungen zur Laufzeit zu erkennen. Entwerfen Sie Datenstrukturen, um Eigentümer und Lebensdauer von allokiertem Speicher klar zu verfolgen. Vermeiden Sie das Speichern von Zeigern an mehreren Stellen, wenn möglich.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Speicher lesen - Wenn der abgelaufene Pointer zum Lesen verwendet wird, können Angreifer möglicherweise Daten beobachten, die durch anderen Code in den neu allokierten Speicher platziert wurden. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz, Beendigung oder Neustart - Der Zugriff auf freigegebenen Speicher kann Abstürze oder Beschädigungen verursachen, die zu Systeminstabilität führen. |
| Integrität, Vertraulichkeit, Verfügbarkeit | Bereich: Integrität, Vertraulichkeit, Verfügbarkeit Unerlaubten Code oder Befehle ausführen - Wenn der Pointer bei Funktionsaufrufen oder Schreibvorgängen verwendet wird, können Angreifer möglicherweise beliebige Codeausführung erreichen. |
Beispielcode
Anfälliger Code
// Anfällig: Use after free
#define SIZE 1024
void vulnerable_use_after_free(int err) {
char* ptr = (char*)malloc(SIZE);
int abrt = 0;
if (err) {
abrt = 1;
free(ptr); // Speicher freigegeben
}
// ... mehr Code ...
if (abrt) {
// Anfällig: ptr wurde freigegeben aber wird noch verwendet
logError("Operation vor Commit abgebrochen", ptr);
}
}
// Anfällig: Double free
void vulnerable_double_free(int condition) {
char* ptr = (char*)malloc(SIZE);
// ... ptr verwenden ...
if (condition) {
free(ptr); // Erste Freigabe
}
// ... mehr Code ...
free(ptr); // Anfällig: Zweite Freigabe desselben Zeigers
}
// Anfällig: Dangling Pointer in Datenstruktur
struct list_node {
int data;
struct list_node *next;
};
void vulnerable_remove_node(struct list_node **head, int value) {
struct list_node *prev = NULL;
struct list_node *curr = *head;
while (curr != NULL) {
if (curr->data == value) {
if (prev) {
prev->next = curr->next;
} else {
*head = curr->next;
}
free(curr); // Speicher freigegeben
// Anfällig: curr zeigt immer noch auf freigegebenen Speicher
break;
}
prev = curr;
curr = curr->next;
}
// Spätere Verwendung von curr wäre gefährlich
printf("Entfernt: %d\n", curr->data); // Use after free!
}
Korrigierter Code
// Korrigiert: Pointer nach free auf NULL setzen und vor Verwendung prüfen
#define SIZE 1024
void fixed_use_after_free(int err) {
char* ptr = (char*)malloc(SIZE);
int abrt = 0;
if (err) {
abrt = 1;
free(ptr);
ptr = NULL; // Korrigiert: Nach free auf NULL setzen
}
if (abrt) {
// Korrigiert: Vor Verwendung prüfen
if (ptr != NULL) {
logError("Operation vor Commit abgebrochen", ptr);
} else {
logError("Operation vor Commit abgebrochen", "(keine Daten)");
}
}
// Aufräumen wenn nicht bereits freigegeben
if (ptr != NULL) {
free(ptr);
}
}
// Korrigiert: Allokationszuständ verfolgen
void fixed_double_free(int condition) {
char* ptr = (char*)malloc(SIZE);
int ptr_freed = 0;
if (condition) {
free(ptr);
ptr = NULL;
ptr_freed = 1;
}
// Korrigiert: Nur freigeben wenn nicht bereits freigegeben
if (!ptr_freed && ptr != NULL) {
free(ptr);
}
}
// Korrigiert: Pointer nach free nicht verwenden
struct list_node {
int data;
struct list_node *next;
};
int fixed_remove_node(struct list_node **head, int value) {
struct list_node *prev = NULL;
struct list_node *curr = *head;
int removed_value = 0;
int found = 0;
while (curr != NULL) {
if (curr->data == value) {
removed_value = curr->data; // Vor free speichern
found = 1;
if (prev) {
prev->next = curr->next;
} else {
*head = curr->next;
}
free(curr);
curr = NULL; // Korrigiert: Sofort auf NULL setzen
break;
}
prev = curr;
curr = curr->next;
}
if (found) {
printf("Entfernt: %d\n", removed_value);
}
return found;
}
// Korrigiert: Smart Pointer verwenden
#include <vector>
#include <memory>
void fixed_smart_pointer() {
// unique_ptr verwaltet automatisch die Lebensdauer
auto ptr = std::make_unique<std::string>("daten");
// Kein manuelles free nötig - automatisch freigegeben wenn out of scope
// Kann nicht versehentlich nach Zerstörung verwendet werden
}
Verwandte CWEs
- CWE-672: Operation auf einer Ressource nach Ablauf oder Freigabe (Eltern)
- CWE-416: Use After Free (Kind - spezifische Variante)
- CWE-415: Double Free (Kind - spezifische Variante)
- CWE-119: Unzureichende Beschränkung von Operationen innerhalb der Grenzen eines Speicherpuffers (verwandt)
- CWE-125: Out-of-bounds-Lesezugriff (kann folgen)
- CWE-787: Out-of-bounds-Schreibzugriff (kann folgen)
Referenzen
- MITRE Corporation. "CWE-825: Expired Pointer Dereference." https://cwe.mitre.org/data/definitions/825.html
- CERT C Secure Coding Standard. "MEM30-C. Do not access freed memory."
- OWASP. "Using Freed Memory." https://owasp.org/www-community/vulnerabilities/Using_freed_memory