Freigabe eines Zeigers, der nicht auf den Pufferanfang zeigt
Beschreibung
Freigabe eines Zeigers, der nicht auf den Pufferanfang zeigt, ist eine Speicherverwaltungsschwachstelle, die auftritt, wenn Software free() auf einen Pointer zu Heap-allokiertem Speicher aufruft, aber der Pointer nicht auf den Anfang des ursprünglich allokierten Puffers verweist. Dies geschieht typischerweise nachdem Zeigerarithmetik den Pointer auf eine innere Position oder hinter das Ende des Puffers verschoben hat. Der Heap-Speicherallokator erwartet, genau den Pointer zu erhalten, der von malloc(), calloc() oder realloc() zurückgegeben wurde. Die Übergabe einer anderen Adresse verursacht Heap-Korruption, was potenziell zu Abstürzen, Speicherkorruption oder ausnutzbaren Zuständen führt.
Risiko
Die Freigabe eines Zeigers, der nicht auf den Pufferanfang zeigt, beschädigt die internen Datenstrukturen des Heaps. Dies kann sofortige Abstürze verursachen, aber gefährlicher ist, dass es zu ausnutzbaren Zuständen führen kann. Angreifer können möglicherweise Heap-Metadaten manipulieren, um beliebige Schreibfähigkeiten zu erlangen, was potenziell zur Codeausführung führt. Die Schwachstelle ist besonders gefährlich, weil sie möglicherweise nicht sofort abstürzt - die Heap-Korruption könnte sich erst später manifestieren, was das Debugging erschwert und Time-of-Check-to-Time-of-Use-Fenster schafft. In einigen Fällen können Angreifer sorgfältig Bedingungen gestalten, um beschädigte Heap-Metadaten für Privilegienerweiterung auszunutzen.
Lösung
Bewahren Sie immer den ursprünglichen Pointer auf, der von Allokationsfunktionen zurückgegeben wurde. Verwenden Sie eine separate Variable oder einen Index zum Durchlaufen des Puffers, während Sie den ursprünglichen Pointer für die Freigabe aufbewahren. In C++ verwenden Sie Smart Pointer (unique_ptr, shared_ptr), die automatisch den Speicher verwalten. Bei der Verwendung von Funktionen wie strtok(), die Pointer modifizieren, denken Sie daran, dass die zurückgegebenen Token in den ursprünglichen Puffer zeigen - geben Sie sie nicht einzeln frei. Erwägen Sie die Verwendung von Container-Klassen oder speichersicheren Sprachen, wo möglich. Verwenden Sie Laufzeitwerkzeuge wie AddressSanitizer oder Valgrind während der Entwicklung, um diese Probleme zu erkennen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Speicher ändern - Freigabe falscher Adressen beschädigt Heap-Metadaten und ermöglicht potenziell Speichermodifikation. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Absturz, Beenden oder Neustart - Heap-Korruption verursacht typischerweise Abstürze. |
| Vertraulichkeit | Bereich: Vertraulichkeit, Integrität, Verfügbarkeit Nicht autorisierten Code oder Befehle ausführen - In einigen Fällen kann Heap-Korruption für Codeausführung ausgenutzt werden. |
Beispielcode
Verwundbarer Code
// Verwundbar: Freigabe eines verschobenen Zeigers nach String-Suche
#include <stdlib.h>
#include <string.h>
char searchChar(char c) {
char* str = malloc(20 * sizeof(char));
if (str == NULL) return -1;
strcpy(str, "Search Me!");
// Durchlaufen des Strings mit Zeigerarithmetik
while (*str != '\0') {
if (*str == c) {
// Verwundbar: str wurde inkrementiert!
free(str); // Freigabe eines inneren Zeigers
return 1; // Gefunden
}
str = str + 1; // Pointer entfernt sich von der ursprünglichen Allokation
}
// Verwundbar: str zeigt jetzt hinter die ursprüngliche Allokation
free(str); // Freigabe falscher Adresse
return 0; // Nicht gefunden
}
// Verwundbar: Freigabe des strtok-Ergebnisses
void vulnerable_tokenize(char* input_copy) {
char* input = malloc(strlen(input_copy) + 1);
strcpy(input, input_copy);
char* tok = strtok(input, " \t");
while (tok != NULL) {
if (is_malformed(tok)) {
// Verwundbar: tok zeigt INNERHALB des input-Puffers
free(tok); // Heap-Korruption!
}
tok = strtok(NULL, " \t");
}
// Ursprünglicher 'input'-Pointer ist verloren, wenn wir versucht haben, Token freizugeben
}
// Verwundbar: Arithmetik vor free
void vulnerable_arithmetic() {
int* array = malloc(10 * sizeof(int));
// Array füllen
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// Verarbeitung mit Zeigerarithmetik
int* ptr = array;
ptr += 5; // Zeigt jetzt auf die Mitte des Arrays
// ... später vergisst der Programmierer, dass ptr verschoben wurde
free(ptr); // Verwundbar: Freigabe der Mitte des Puffers
}
// Verwundbar: Inkrementieren des Zeigers in Schleife, dann Freigabe
char* vulnerable_skip_whitespace(char* original) {
char* str = malloc(100);
strcpy(str, original);
// Führende Leerzeichen überspringen
while (*str == ' ' || *str == '\t') {
str++; // Pointer bewegt sich vorwärts
}
// ... str verwenden ...
process(str);
// Verwundbar: str zeigt nicht mehr auf den Allokationsanfang
free(str); // Heap-Korruption
return NULL;
}
// Verwundbar: Funktion gibt inneren Pointer zurück
char* vulnerable_find_substring(char* haystack, char* needle) {
char* buffer = malloc(strlen(haystack) + 1);
strcpy(buffer, haystack);
char* found = strstr(buffer, needle);
// Verwundbar: Rückgabe eines Zeigers in den Puffer
// Aufrufer könnte versuchen, diesen Pointer freizugeben
return found; // Zeigt in den Puffer, nicht an den Anfang
}
void use_vulnerable() {
char* result = vulnerable_find_substring("Hello World", "World");
if (result) {
printf("Found: %s\n", result);
free(result); // ABSTURZ: Freigabe eines inneren Zeigers
}
}
Korrigierter Code
// Korrigiert: Index statt Zeigerarithmetik verwenden
#include <stdlib.h>
#include <string.h>
char searchChar_fixed(char c) {
char* str = malloc(20 * sizeof(char));
if (str == NULL) return -1;
strcpy(str, "Search Me!");
// Korrigiert: Index verwenden, ursprünglichen Pointer beibehalten
int i = 0;
while (i < strlen(str)) {
if (str[i] == c) {
free(str); // Korrigiert: Ursprünglicher Pointer unverändert
return 1;
}
i = i + 1; // Index wird inkrementiert, nicht Pointer
}
free(str); // Korrigiert: Immer noch der ursprüngliche Pointer
return 0;
}
// Korrigiert: Token kopieren statt sie freizugeben
void fixed_tokenize(char* input_copy) {
char* input = malloc(strlen(input_copy) + 1);
if (input == NULL) return;
strcpy(input, input_copy);
char* tok = strtok(input, " \t");
while (tok != NULL) {
if (!is_malformed(tok)) {
// Korrigiert: Token in neue Allokation kopieren
char* command = malloc((strlen(tok) + 1) * sizeof(char));
if (command != NULL) {
strcpy(command, tok);
add_to_command_queue(command); // Übernimmt Besitz
}
}
tok = strtok(NULL, " \t");
}
// Korrigiert: Ursprünglichen Puffer einmal am Ende freigeben
free(input);
}
// Korrigiert: Ursprünglichen Pointer aufbewahren
void fixed_arithmetic() {
int* array = malloc(10 * sizeof(int));
if (array == NULL) return;
// Array füllen
for (int i = 0; i < 10; i++) {
array[i] = i;
}
// Korrigiert: Separaten Pointer zum Durchlaufen verwenden
int* ptr = array + 5; // Temporärer Pointer für Zugriff
// ... ptr zum Lesen verwenden ...
process(ptr);
// Korrigiert: Ursprünglichen Pointer freigeben
free(array); // Korrekt: ursprüngliche Allokation freigeben
}
// Korrigiert: Ursprünglichen Pointer für Aufräumen aufbewahren
char* fixed_skip_whitespace(char* original, char** out_start) {
char* str = malloc(100);
if (str == NULL) return NULL;
strcpy(str, original);
// Korrigiert: Separaten Pointer zum Durchlaufen verwenden
char* current = str;
// Führende Leerzeichen überspringen
while (*current == ' ' || *current == '\t') {
current++;
}
// Beide Pointer zurückgeben
*out_start = current; // Wo der Inhalt beginnt
return str; // Ursprüngliche Allokation (zum Freigeben)
}
void use_fixed() {
char* content_start;
char* allocation = fixed_skip_whitespace(" Hello ", &content_start);
if (allocation) {
process(content_start); // Inhalt verwenden
free(allocation); // Ursprünglichen Pointer freigeben
}
}
// Korrigiert: Offset oder Kopie zurückgeben statt inneren Pointer
typedef struct {
char* buffer;
size_t offset;
} SubstringResult;
SubstringResult fixed_find_substring(char* haystack, char* needle) {
SubstringResult result = {NULL, 0};
char* buffer = malloc(strlen(haystack) + 1);
if (buffer == NULL) return result;
strcpy(buffer, haystack);
char* found = strstr(buffer, needle);
result.buffer = buffer; // Ursprüngliche Allokation
if (found) {
result.offset = found - buffer; // Offset zurückgeben, nicht Pointer
} else {
result.offset = (size_t)-1; // Nicht-gefunden-Indikator
}
return result;
}
void use_fixed_substring() {
SubstringResult result = fixed_find_substring("Hello World", "World");
if (result.buffer && result.offset != (size_t)-1) {
printf("Found at offset %zu: %s\n",
result.offset, result.buffer + result.offset);
}
free(result.buffer); // Korrigiert: Ursprüngliche Allokation freigeben
}
// Korrigiert: C++ mit Smart Pointern
#include <memory>
#include <string>
#include <vector>
class SecureStringProcessor {
public:
bool searchChar(char c) {
// Korrigiert: unique_ptr verwaltet Speicher automatisch
auto str = std::make_unique<char[]>(20);
strcpy(str.get(), "Search Me!");
// String sicher verwenden
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == c) {
return true;
}
}
return false;
// Speicher wird automatisch freigegeben, wenn str den Gültigkeitsbereich verlässt
}
// Noch besser: std::string verwenden
bool searchCharString(char c) {
std::string str = "Search Me!";
for (char ch : str) {
if (ch == c) {
return true;
}
}
return false;
// Keine manuelle Speicherverwaltung erforderlich
}
};
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.
- CVE-2010-2547: Double-Free-Schwachstelle durch Freigabe eines inneren Zeigers verursacht.
Referenzen
- MITRE Corporation. "CWE-761: Free of Pointer not at Start of Buffer." https://cwe.mitre.org/data/definitions/761.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."