Rückgabe der Adresse einer Stack-Variablen
Beschreibung
Rückgabe der Adresse einer Stack-Variablen tritt auf, wenn eine Funktion einen Pointer oder eine Referenz auf eine lokale Variable zurückgibt, die nicht mehr existiert, wenn die Funktion zurückkehrt. Lokale (automatische) Variablen werden auf dem Stack alloziert und beim Verlassen der Funktion freigegeben. Die Rückgabe ihrer Adresse liefert einen hängenden Pointer - der Speicher wird zurückgewonnen und kann für andere Zwecke wiederverwendet werden. Jeder nachfolgende Zugriff über diesen Pointer liest oder schreibt Speicher, der nicht mehr zur Variablen gehört.
Risiko
Die Verwendung eines hängenden Zeigers auf Stack-Speicher verursacht undefiniertes Verhalten, Abstürze und Sicherheitsschwachstellen. Der Speicher kann Garbage-Daten enthalten, was zu Informationsoffenlegung von Stack-Inhalten führt. Wenn der Stack-Speicher wiederverwendet wird (durch einen anderen Funktionsaufruf), beschädigen Schreibvorgänge über den hängenden Pointer die Daten der neuen Funktion oder Rücksprungadressen, was Code-Ausführung ermöglicht. Der Bug ist besonders gefährlich, weil er während Tests funktionieren kann (wenn der Speicher nicht wiederverwendet wird), aber unvorhersehbar in der Produktion fehlschlägt.
Lösung
Geben Sie niemals Pointer oder Referenzen auf lokale Variablen zurück. Verwenden Sie statische oder globale Variablen, wenn die Daten persistieren müssen (unter Berücksichtigung der Thread-Sicherheit). Allozieren Sie Speicher dynamisch (Heap) und geben Sie diesen Pointer zurück (Aufrufer muss freigeben). Übergeben Sie einen vom Aufrufer bereitgestellten Puffer zum Befüllen. Geben Sie kleine Objekte per Wert zurück. In C++ verwenden Sie Smart-Pointer oder geben Sie std::optional/std::variant für optionale Ergebnisse zurück. Aktivieren Sie Compiler-Warnungen (-Wreturn-local-addr).
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Speicherkorruption Schreiben über hängenden Pointer beschädigt Stack-Speicher. |
| Vertraulichkeit | Bereich: Informationsoffenlegung Lesen über hängenden Pointer legt Stack-Inhalte offen. |
| Verfügbarkeit | Bereich: Absturz/Code-Ausführung Stack-Korruption kann abstürzen oder Code-Ausführung ermöglichen. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Adresse lokaler Variable zurückgeben
char* get_greeting_vulnerable() {
char greeting[100] = "Hallo, Welt!";
return greeting; // BUG! Gibt Adresse lokaler Variable zurück!
}
void caller_vulnerable() {
char* msg = get_greeting_vulnerable();
printf("%s\n", msg); // Undefiniertes Verhalten! Stack könnte wiederverwendet sein!
}
// VERWUNDBAR: Pointer auf lokale zurückgeben
int* get_value_vulnerable() {
int value = 42;
return &value; // BUG! value existiert nicht mehr!
}
// VERWUNDBAR: Adresse eines lokalen Array-Elements zurückgeben
int* get_element_vulnerable(int index) {
int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
return &array[index]; // BUG! array ist lokal!
}
// VERWUNDBAR: Pointer aus Bedingung zurückgeben
char* get_name_vulnerable(int id) {
if (id == 1) {
char name[] = "Alice";
return name; // BUG! Lokale Variable!
} else {
char name[] = "Bob";
return name; // BUG! Beide Zweige geben lokal zurück!
}
}
// VERWUNDBAR: Struct mit Pointer auf lokale
struct Result {
int* data;
int size;
};
struct Result process_vulnerable(int input) {
int local_data[5];
struct Result result;
for (int i = 0; i < 5; i++) {
local_data[i] = input + i;
}
result.data = local_data; // BUG! Zeigt auf lokale!
result.size = 5;
return result; // result.data ist hängend!
}
// VERWUNDBAR: C++ Referenz auf lokale
int& get_reference_vulnerable() {
int local = 42;
return local; // BUG! Referenz auf lokale!
}
// VERWUNDBAR: Adresse eines lokalen Objekts zurückgeben
std::string* get_string_vulnerable() {
std::string local = "Hallo";
return &local; // BUG! Objekt beim Return zerstört!
}
// VERWUNDBAR: Lambda erfasst lokale per Referenz
auto get_lambda_vulnerable() {
int local = 42;
return [&local]() { return local; }; // BUG! local ist weg!
}
// VERWUNDBAR: Iterator auf lokalen Container
std::vector<int>::iterator get_iterator_vulnerable() {
std::vector<int> local = {1, 2, 3};
return local.begin(); // BUG! vector zerstört!
}
// VERWUNDBAR: string_view auf lokalen String
std::string_view get_view_vulnerable() {
std::string local = "Hallo";
return local; // BUG! Implizite Konvertierung zu View eines zerstörten Strings!
}
Lösungscode
// SICHER: Per Wert zurückgeben (für kleine Daten)
int get_value_safe() {
int value = 42;
return value; // Gibt Kopie zurück, nicht Adresse
}
// SICHER: Statischen Speicher verwenden (Vorsicht bei Thread-Sicherheit!)
const char* get_greeting_static() {
static char greeting[] = "Hallo, Welt!";
return greeting; // Statisch lebt für Programm-Lebensdauer
}
// SICHER: Dynamisch allozieren (Aufrufer muss freigeben)
char* get_greeting_heap() {
char* greeting = malloc(100);
if (greeting) {
strcpy(greeting, "Hallo, Welt!");
}
return greeting; // Aufrufer muss free()!
}
// SICHER: Aufrufer stellt Puffer bereit
void get_greeting_buffer(char* buffer, size_t size) {
snprintf(buffer, size, "Hallo, Welt!");
}
// Aufrufverwendung:
void caller_safe() {
char buffer[100];
get_greeting_buffer(buffer, sizeof(buffer));
printf("%s\n", buffer);
}
// SICHER: Pointer auf Element eines statischen Arrays zurückgeben
int* get_element_safe(int index) {
static int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
if (index >= 0 && index < 10) {
return &array[index];
}
return NULL;
}
// SICHER: String-Literal verwenden (hat statischen Speicher)
const char* get_name_literal(int id) {
if (id == 1) {
return "Alice"; // String-Literale haben statischen Speicher
} else {
return "Bob";
}
}
// SICHER: Struct mit dynamisch allozierten Daten
struct Result process_safe(int input) {
struct Result result;
result.data = malloc(5 * sizeof(int));
if (result.data == NULL) {
result.size = 0;
return result;
}
for (int i = 0; i < 5; i++) {
result.data[i] = input + i;
}
result.size = 5;
return result; // Aufrufer muss result.data freigeben
}
// SICHER: Per Wert zurückgeben
int get_value_cpp_safe() {
int local = 42;
return local; // Kopie zurückgegeben
}
// SICHER: String per Wert zurückgeben
std::string get_string_safe() {
std::string local = "Hallo";
return local; // Kopie oder Move-Semantik
}
// SICHER: Smart-Pointer zurückgeben
std::unique_ptr<std::string> get_string_smart() {
return std::make_unique<std::string>("Hallo");
}
// SICHER: Lambda erfasst per Wert
auto get_lambda_safe() {
int local = 42;
return [local]() { return local; }; // Erfasst Kopie
}
// SICHER: Container per Wert zurückgeben
std::vector<int> get_vector_safe() {
return {1, 2, 3}; // Per Wert zurückgegeben
}
// SICHER: optional für Maybe-Ergebnisse zurückgeben
std::optional<int> get_optional_safe(bool valid) {
if (valid) {
int local = 42;
return local; // Kopiert Wert in optional
}
return std::nullopt;
}
// SICHER: shared_ptr für geteilten Besitz
std::shared_ptr<Resource> create_resource_safe() {
return std::make_shared<Resource>();
}
// SICHER: Move-Semantik für Effizienz
std::string create_string_safe() {
std::string local = "Hallo";
local += " Welt";
return local; // Move-Semantik (RVO/NRVO)
}
Ausgenutzt in der Praxis
Stack-Puffer-Wiederverwendungs-Exploits
Angreifer haben hängende Pointer auf Stack-Speicher ausgenutzt, indem sie Funktionen aufrufen, die den Stack-Platz wiederverwenden, und dann die Daten über den hängenden Pointer manipulieren.
Informationsoffenlegung via Stack
Lesen über hängende Stack-Pointer hat sensible Daten wie Funktionsargumente, Rücksprungadressen und lokale Variablen anderer Funktionen offengelegt.
Rücksprungadressen-Überschreibung
Schreiben über hängende Stack-Pointer wurde verwendet, um Rücksprungadressen zu überschreiben, was beliebige Code-Ausführung ermöglicht.
Tools zum Testen
- GCC/Clang -Wreturn-local-addr — warnt vor Rückgabe lokaler Adressen.
- AddressSanitizer — erkennt Use-after-Return.
- Valgrind — erkennt ungültigen Speicherzugriff.
- Coverity — statische Analyse für hängende Pointer.
CVE-Beispiele
- CVE-2015-0235 — GHOST-Schwachstelle mit Pufferproblemen.
- Mehrere CVEs mit Stack-Puffer-Wiederverwendung und hängenden Zeigern.
- Verschiedene Use-after-Return-Schwachstellen in Browsern und Systemsoftware.
Referenzen
- MITRE. "CWE-562: Return of Stack Variable Address." https://cwe.mitre.org/data/definitions/562.html
- CERT C. "DCL30-C: Declare objects with appropriate storage durations."