Freigabe eines ungültigen Zeigers oder einer ungültigen Referenz
Beschreibung
Freigabe eines ungültigen Zeigers oder einer ungültigen Referenz ist eine Speicherverwaltungsschwachstelle, bei der Software versucht, eine Speicherressource an das System zurückzugeben, aber entweder die falsche Freigabefunktion aufruft oder die entsprechende Freigabefunktion mit einem ungültigen Pointer aufruft. Dies umfasst sowohl nicht übereinstimmende Allokations-/Freigabepaare (wie die Verwendung von free() auf mit new allokiertem Speicher) als auch die Übergabe falscher Pointer an korrekte Funktionen (wie das Freigeben eines Zeigers, der durch Zeigerarithmetik verschoben wurde). Das Ergebnis ist undefiniertes Verhalten, das zu Speicherkorruption, Abstürzen oder ausnutzbaren Zuständen führen kann.
Risiko
Die Freigabe ungültiger Pointer beschädigt Heap-Metadaten und interne Speicherverwaltungsstrukturen. Dies kann sofortige Abstürze verursachen, aber gefährlicher ist, dass es Speicher, der später vom Programm verwendet wird, stillschweigend beschädigen kann. Angreifer können möglicherweise Heap-Korruption für beliebige Codeausführung ausnutzen, indem sie freigegebene Speicherstrukturen manipulieren. Die Schwachstelle ist besonders tückisch, weil die Korruption sich möglicherweise erst viel später in der Programmausführung manifestiert, was das Debugging extrem erschwert. Funktionen wie strtok() und strsep() sind häufige Quellen dieses Fehlers, weil sie Pointer in einen bestehenden Puffer zurückgeben, den Aufrufer möglicherweise fälschlicherweise freizugeben versuchen.
Lösung
Geben Sie immer genau den Pointer frei, der von der Allokationsfunktion zurückgegeben wurde. Geben Sie keine Pointer frei, die durch Zeigerarithmetik modifiziert wurden - bewahren Sie den ursprünglichen Pointer für die Freigabe auf. Verstehen Sie, welche Funktionen neu allokierten Speicher zurückgeben versus Pointer in bestehenden Speicher. Verwenden Sie separate Variablen zum Durchlaufen, während Sie den ursprünglichen Allokationszeiger aufbewahren. In C++ verwenden Sie Smart Pointer, die automatisch den Speicher korrekt verwalten. Verwenden Sie Laufzeitwerkzeuge wie AddressSanitizer oder Valgrind, um ungültige Freigaben während der Entwicklung zu erkennen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Speicher ändern - Ungültige Zeigerfreigabe beschädigt Heap-Metadaten und ermöglicht potenziell Speichermodifikation. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz, Beenden oder Neustart - Speicherkorruption verursacht typischerweise Abstürze. |
| Vertraulichkeit | Bereich: Vertraulichkeit, Integrität, Verfügbarkeit Nicht autorisierten Code oder Befehle ausführen - Heap-Korruption kann manchmal für Codeausführung ausgenutzt werden. |
Beispielcode
Verwundbarer Code
// Verwundbar: Freigabe von strsep-Token (zeigt in ursprünglichen Puffer)
#include <stdlib.h>
#include <string.h>
void vulnerable_strsep() {
char* inputstring = strdup("first second third fourth");
char* ap[4];
char** inputstringp = &inputstring;
// strsep modifiziert inputstring und gibt Pointer HINEIN zurück
ap[0] = strsep(inputstringp, " "); // Zeigt in inputstring
ap[1] = strsep(inputstringp, " ");
ap[2] = strsep(inputstringp, " ");
ap[3] = strsep(inputstringp, " ");
// Verwundbar: ap[0-3] zeigen INNERHALB von inputstring, nicht separate Allokationen!
free(ap[0]); // Beschädigt Heap - Freigabe inneren Zeigers
free(ap[1]); // Doppelte Korruption
free(ap[2]); // Mehr Korruption
free(ap[3]); // Noch mehr Korruption
}
// Verwundbar: Freigabe von strtok-Ergebnis
void vulnerable_strtok() {
char* input = malloc(100);
strcpy(input, "one two three");
char* tok = strtok(input, " ");
while (tok != NULL) {
if (is_invalid(tok)) {
// Verwundbar: tok zeigt innerhalb des input-Puffers
free(tok); // Ungültige Freigabe!
}
tok = strtok(NULL, " ");
}
// Ursprünglicher input-Pointer kann verloren sein
}
// Verwundbar: Zeigerarithmetik vor free
void vulnerable_arithmetic() {
char* str = malloc(20);
strcpy(str, "Search Me!");
// Durch String laufen
while (*str != '\0') {
if (*str == 'M') {
// Verwundbar: str wurde inkrementiert
free(str); // Freigabe inneren Zeigers!
return;
}
str = str + 1; // Pointer bewegt sich
}
free(str); // Immer noch falsch - str ist am Ende des Puffers
}
// Verwundbar: Nicht übereinstimmende Allokation/Freigabe
void vulnerable_mismatch() {
// Mit new allokiert
BarObj* ptr = new BarObj();
// Verwundbar: Mit falscher Funktion freigegeben
free(ptr); // Sollte delete sein
}
// Verwundbar: Stack-Speicher freigeben
void vulnerable_stack_free() {
int localVar = 42;
int* ptr = &localVar;
// Verwundbar: Kann Stack-Speicher nicht freigeben
free(ptr); // Undefiniertes Verhalten
}
// Verwundbar: Double Free durch Aliasing
void vulnerable_double_free() {
char* ptr1 = (char*)malloc(100);
char* ptr2 = ptr1; // Alias
// ... Speicher verwenden ...
free(ptr1);
// Verwundbar: ptr2 ist jetzt hängend
free(ptr2); // Double Free!
}
// Verwundbar: Funktion gibt inneren Pointer zurück
char* vulnerable_find_word(const char* text, const char* word) {
char* buffer = strdup(text);
char* found = strstr(buffer, word);
// Verwundbar: Aufrufer könnte versuchen, zurückgegebenen Pointer freizugeben
// der innerhalb von buffer ist, nicht an seinem Anfang
return found; // Innerer Pointer oder NULL
// buffer ist geleakt, und found kann nicht korrekt freigegeben werden
}
void use_vulnerable() {
char* result = vulnerable_find_word("Hello World", "World");
if (result) {
printf("Found: %s\n", result);
free(result); // ABSTURZ: Ungültige Freigabe
}
}
Korrigierter Code
// Korrigiert: Nur die ursprüngliche Allokation freigeben
#include <stdlib.h>
#include <string.h>
void fixed_strsep() {
char* inputstring = strdup("first second third fourth");
char* original = inputstring; // Original zum Freigeben aufbewahren
char* ap[4];
ap[0] = strsep(&inputstring, " ");
ap[1] = strsep(&inputstring, " ");
ap[2] = strsep(&inputstring, " ");
ap[3] = strsep(&inputstring, " ");
// Die Token verwenden...
for (int i = 0; i < 4 && ap[i] != NULL; i++) {
process(ap[i]);
}
// Korrigiert: Nur die ursprüngliche Allokation freigeben
free(original);
}
// Korrigiert: Token kopieren wenn man sie besitzen muss
void fixed_strtok() {
char* input = malloc(100);
strcpy(input, "one two three");
char* tok = strtok(input, " ");
while (tok != NULL) {
if (!is_invalid(tok)) {
// Korrigiert: Token in eigene Allokation kopieren
char* copy = strdup(tok);
add_to_list(copy); // Liste übernimmt Besitz der Kopie
}
tok = strtok(NULL, " ");
}
// Korrigiert: Ursprünglichen Puffer einmal freigeben
free(input);
}
// Korrigiert: Index verwenden statt Pointer zu verschieben
void fixed_arithmetic() {
char* str = malloc(20);
if (str == NULL) return;
strcpy(str, "Search Me!");
// Korrigiert: Index verwenden, Pointer nicht verschieben
int i = 0;
while (str[i] != '\0') {
if (str[i] == 'M') {
break;
}
i++;
}
// Korrigiert: str ist immer noch der ursprüngliche Pointer
free(str);
}
// Alternative: Ursprünglichen Pointer speichern
void fixed_save_original() {
char* original = malloc(20);
if (original == NULL) return;
strcpy(original, "Search Me!");
char* current = original; // Separater Durchlaufzeiger
while (*current != '\0') {
if (*current == 'M') {
break;
}
current++;
}
// Korrigiert: Original freigeben, nicht Durchlaufzeiger
free(original);
}
// Korrigiert: Übereinstimmende Allokation/Freigabe
void fixed_matching() {
// new -> delete
BarObj* ptr1 = new BarObj();
delete ptr1;
// malloc -> free
BarObj* ptr2 = (BarObj*)malloc(sizeof(BarObj));
free(ptr2);
// new[] -> delete[]
int* arr = new int[100];
delete[] arr;
}
// Korrigiert: Smart Pointer verwenden
#include <memory>
void fixed_smart_pointer() {
auto ptr = std::make_unique<BarObj>();
// Automatische korrekte Freigabe
}
// Korrigiert: Double Free mit Null-Zuweisung verhindern
void fixed_double_free() {
char* ptr = (char*)malloc(100);
if (ptr == NULL) return;
// ... Speicher verwenden ...
free(ptr);
ptr = NULL; // Versehentliche Wiederverwendung verhindern
// Sicher: free(NULL) ist definiert als no-op
// Aber besser es gar nicht aufzurufen
}
// Korrigiert: Korrekt allokiertes Ergebnis zurückgeben
typedef struct {
char* buffer; // Ursprüngliche Allokation
char* found; // Position innerhalb des Puffers (oder NULL)
} FindResult;
FindResult fixed_find_word(const char* text, const char* word) {
FindResult result = {NULL, NULL};
result.buffer = strdup(text);
if (result.buffer == NULL) return result;
result.found = strstr(result.buffer, word);
return result;
}
void use_fixed() {
FindResult result = fixed_find_word("Hello World", "World");
if (result.found) {
printf("Found: %s\n", result.found);
}
// Korrigiert: Den Puffer freigeben, nicht den inneren Pointer
free(result.buffer);
}
// Alternative: Kopie oder Offset zurückgeben
size_t fixed_find_offset(const char* text, const char* word) {
char* found = strstr(text, word);
if (found == NULL) {
return (size_t)-1; // Nicht-gefunden-Indikator
}
return found - text; // Offset zurückgeben, nicht Pointer
}
CVE-Beispiele
- CVE-2019-11930: Funktion rief intern calloc auf und gab einen Pointer an einem Index innerhalb des allokierten Puffers zurück, was zu ungültiger Speicherfreigabe führte, wenn der Aufrufer ihn freigab.
- CVE-2015-0821: Freigabe eines ungültigen Zeigers verursachte Speicherkorruption.
Referenzen
- MITRE Corporation. "CWE-763: Release of Invalid Pointer or Reference." https://cwe.mitre.org/data/definitions/763.html
- CERT C Coding Standard. "MEM34-C. Only free memory allocated dynamically."
- CERT C Coding Standard. "MEM31-C. Free dynamically allocated memory when no longer needed."