Fehlende Speicherfreigabe nach effektiver Lebensdauer (Speicherleck)
Beschreibung
Fehlende Speicherfreigabe nach effektiver Lebensdauer tritt auf, wenn ein Programm Speicher alloziert, aber versäumt, ihn freizugeben, nachdem er nicht mehr benötigt wird. Dies führt zu Speicherlecks, bei denen der Speicherverbrauch des Programms kontinuierlich wächst. In lang laufenden Anwendungen können Speicherlecks schließlich den verfügbaren Speicher erschöpfen und Abstürze, Systeminstabilität oder Denial-of-Service verursachen. Speicherlecks sind besonders gefährlich in Servern, Daemons und eingebetteten Systemen, wo Anwendungen kontinuierlich laufen.
Risiko
Speicherlecks sind ein erhebliches Zuverlässigkeits- und Verfügbarkeitsproblem. In Webservern kann jede Anfrage, die Speicher leckt, schließlich den Server zum Absturz bringen. In eingebetteten Systemen mit begrenztem Speicher werden Lecks schnell kritisch. Speichererschöpfung kann zu Denial-of-Service führen. Lecks können auch andere Schwachstellen maskieren – Speicherkorruption stürzt möglicherweise nicht sofort ab, sondern erst wenn der Speicher fragmentiert wird. Moderne Systeme mit großen Mengen RAM können Lecks verbergen, bis sie plötzlich kritisch werden.
Lösung
Verwenden Sie Speicherverwaltungstools (Valgrind, AddressSanitizer) während der Entwicklung und beim Testen. Implementieren Sie konsistente Allokations-/Deallokationsmuster. Verwenden Sie RAII (Resource Acquisition Is Initialization) in C++ mit Smart Pointern. Erwägen Sie Garbage-Collection-Sprachen für komplexe Anwendungen. Implementieren Sie Referenzzählung für gemeinsam genutzte Ressourcen. Verwenden Sie statische Analysetools zur Leckerkennung. Überwachen Sie Produktionssysteme auf Speicherwachstum. Implementieren Sie ordnungsgemäße Fehlerbehandlung, die Allokationen auf Fehlerpfaden bereinigt.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Umfang: Denial of Service Kontinuierliche Speicherlecks erschöpfen schließlich den verfügbaren Speicher und bringen die Anwendung zum Absturz. |
| Leistung | Umfang: Degradation Speicherdruck verursacht Swapping, Garbage-Collection-Druck und allgemeine Verlangsamung. |
| Zuverlässigkeit | Umfang: Systeminstabilität Speichererschöpfung kann andere Prozesse auf demselben System beeinträchtigen. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Speicher alloziert aber nie freigegeben
void process_request(const char *data) {
char *buffer = malloc(1024);
strcpy(buffer, data);
process(buffer);
// Fehlt: free(buffer);
// Speicher leckt bei jedem Aufruf!
}
// ANFÄLLIG: Frühe Rückgabe ohne Freigabe
int read_config(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) return -1;
char *buffer = malloc(4096);
if (!buffer) {
fclose(fp);
return -1;
}
if (fread(buffer, 1, 4096, fp) == 0) {
fclose(fp);
return -1; // Leckt buffer!
}
// Konfiguration verarbeiten...
free(buffer);
fclose(fp);
return 0;
}
// ANFÄLLIG: Verlorener Pointer
void create_list(void) {
Node *head = malloc(sizeof(Node));
head->next = malloc(sizeof(Node));
head->next->next = malloc(sizeof(Node));
// head neu zuweisen - Referenz zu alloziertem Speicher verlieren!
head = malloc(sizeof(Node)); // Ursprüngliche Kette leckt!
}
// ANFÄLLIG: Exception-ähnlicher Pfad leckt
int complex_operation(void) {
void *resource1 = malloc(100);
void *resource2 = malloc(200);
void *resource3 = malloc(300);
if (step1(resource1) != 0) {
free(resource1);
return -1; // Leckt resource2, resource3!
}
if (step2(resource2) != 0) {
free(resource1);
free(resource2);
return -1; // Leckt resource3!
}
// ... fortsetzen ...
free(resource1);
free(resource2);
free(resource3);
return 0;
}
// ANFÄLLIG: C++ Roh-Pointer im Exception-Pfad
class DataProcessor {
int* data;
public:
DataProcessor() {
data = new int[1000];
}
void process() {
// Wenn Exception geworfen wird, wird Destruktor für lokale Objekte nicht aufgerufen
DataProcessor* temp = new DataProcessor();
throw std::runtime_error("Fehler"); // temp leckt!
delete temp;
}
~DataProcessor() {
delete[] data;
}
};
// ANFÄLLIG: Container von Roh-Zeigern
void process_items() {
std::vector<Item*> items;
for (int i = 0; i < 100; i++) {
items.push_back(new Item());
}
// Vector zerstört aber Items nicht gelöscht - alle lecken!
}
# ANFÄLLIG: Python zirkulare Referenz (GC behandelt normalerweise, aber nicht immer)
class Node:
def __init__(self):
self.ref = None
self.data = bytearray(1024 * 1024) # 1MB
def create_cycle():
a = Node()
b = Node()
a.ref = b
b.ref = a # Zirkulare Referenz
# Mit benutzerdefiniertem __del__ sammelt Garbage Collector möglicherweise nicht
# ANFÄLLIG: Datei-Handles nicht geschlossen (Ressourcenleck)
def read_files(filenames):
contents = []
for fname in filenames:
f = open(fname, 'r') # Nie geschlossen!
contents.append(f.read())
return contents
Korrigierter Code
// SICHER: Allozierten Speicher immer freigeben
void process_request_safe(const char *data) {
char *buffer = malloc(1024);
if (!buffer) return;
strcpy(buffer, data);
process(buffer);
free(buffer); // Immer freigeben
}
// SICHER: Einzelner Cleanup-Pfad
int read_config_safe(const char *filename) {
FILE *fp = NULL;
char *buffer = NULL;
int result = -1;
fp = fopen(filename, "r");
if (!fp) goto cleanup;
buffer = malloc(4096);
if (!buffer) goto cleanup;
if (fread(buffer, 1, 4096, fp) == 0) {
goto cleanup; // Einzelner Cleanup-Pfad
}
// Konfiguration verarbeiten...
result = 0;
cleanup:
free(buffer); // free(NULL) ist sicher
if (fp) fclose(fp);
return result;
}
// SICHER: Alle Allokationen verfolgen
void create_list_safe(void) {
Node *head = malloc(sizeof(Node));
if (!head) return;
head->next = malloc(sizeof(Node));
if (!head->next) {
free(head);
return;
}
head->next->next = malloc(sizeof(Node));
if (!head->next->next) {
free(head->next);
free(head);
return;
}
// Liste verwenden...
// Gesamte Liste freigeben
free(head->next->next);
free(head->next);
free(head);
}
// SICHER: Konsistentes Cleanup-Muster
typedef struct {
void *resource1;
void *resource2;
void *resource3;
} Resources;
void cleanup_resources(Resources *r) {
free(r->resource1);
free(r->resource2);
free(r->resource3);
memset(r, 0, sizeof(*r));
}
int complex_operation_safe(void) {
Resources r = {0};
r.resource1 = malloc(100);
r.resource2 = malloc(200);
r.resource3 = malloc(300);
if (!r.resource1 || !r.resource2 || !r.resource3) {
cleanup_resources(&r);
return -1;
}
if (step1(r.resource1) != 0) {
cleanup_resources(&r);
return -1;
}
if (step2(r.resource2) != 0) {
cleanup_resources(&r);
return -1;
}
// Erfolgspfad bereinigt auch
cleanup_resources(&r);
return 0;
}
// SICHER: Smart Pointer verwenden
#include <memory>
#include <vector>
class DataProcessorSafe {
std::unique_ptr<int[]> data;
public:
DataProcessorSafe() : data(std::make_unique<int[]>(1000)) {}
void process() {
// Smart Pointer behandelt Cleanup auch bei Exceptions
auto temp = std::make_unique<DataProcessorSafe>();
throw std::runtime_error("Fehler"); // temp wird automatisch bereinigt!
}
// Destruktor nicht nötig - unique_ptr behandelt es
};
// SICHER: Container von Smart Pointern
void process_items_safe() {
std::vector<std::unique_ptr<Item>> items;
for (int i = 0; i < 100; i++) {
items.push_back(std::make_unique<Item>());
}
// Vector zerstört - alle Items automatisch gelöscht!
}
// SICHER: RAII-Wrapper für C-Ressourcen
class FileHandle {
FILE* fp;
public:
explicit FileHandle(const char* name, const char* mode)
: fp(fopen(name, mode)) {
if (!fp) throw std::runtime_error("Kann Datei nicht öffnen");
}
~FileHandle() {
if (fp) fclose(fp);
}
// Kopie löschen
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// Move erlauben
FileHandle(FileHandle&& other) noexcept : fp(other.fp) {
other.fp = nullptr;
}
FILE* get() { return fp; }
};
// Verwendung - automatischer Cleanup
void process_file(const char* filename) {
FileHandle fh(filename, "r");
// fh.get() verwenden...
// Automatisch geschlossen wenn fh den Gültigkeitsbereich verlässt
}
# SICHER: Kontextmanager verwenden
def read_files_safe(filenames):
contents = []
for fname in filenames:
with open(fname, 'r') as f: # Automatisch geschlossen
contents.append(f.read())
return contents
# SICHER: Weak References um Zyklen zu brechen
import weakref
class Node:
def __init__(self):
self._ref = None
self.data = bytearray(1024 * 1024)
@property
def ref(self):
return self._ref() if self._ref else None
@ref.setter
def ref(self, value):
self._ref = weakref.ref(value) if value else None
def create_no_cycle():
a = Node()
b = Node()
a.ref = b # Normale Referenz
b.ref = a # Weak Reference - verhindert Collection nicht
# SICHER: Expliziter Cleanup
class ManagedResource:
def __init__(self):
self.resource = allocate_large_resource()
def __enter__(self):
return self
def __exit__(self, *args):
self.cleanup()
def cleanup(self):
if self.resource:
release_resource(self.resource)
self.resource = None
# Verwendung
with ManagedResource() as r:
use_resource(r)
# Automatisch bereinigt
Ausgenutzt in der Praxis
Microsoft Windows LSASS Speicherleck (2003)
Speicherlecks in LSASS (Local Security Authority Subsystem Service) könnten aus der Ferne ausgelöst werden und verursachten schließlich Systemabstürze und Denial-of-Service.
OpenSSL Speicherleck DoS (2016)
CVE-2016-6304 war ein Speicherleck in OpenSSLs OCSP-Antwortverarbeitung, das für Denial-of-Service ausgenutzt werden könnte, indem wiederholte Statusanfragen gesendet wurden.
Firefox Speicherlecks
Mehrere Speicherlecks in Firefox wurden über die Jahre gemeldet, die den Speicherverbrauch des Browsers während länger Browsing-Sitzungen erheblich wachsen ließen.
Tools zum Testen/Ausnutzen
-
Valgrind — Umfassende Speicherleck-Erkennung.
-
AddressSanitizer — Schneller Speicherfehler-Detektor mit Leck-Prüfung.
-
LeakSanitizer — Eigenständiger Leck-Detektor.
-
Visual Studio Memory Diagnostics — Windows Speicher-Profiling.
CVE-Beispiele
-
CVE-2016-6304 — OpenSSL OCSP Speicherleck DoS.
-
CVE-2019-1559 — OpenSSL Speicherleck bei Padding Oracle.
-
CVE-2017-7668 — Apache HTTP Server Speicherleck.
Referenzen
-
MITRE. "CWE-401: Missing Release of Memory after Effective Lifetime." https://cwe.mitre.org/data/definitions/401.html
-
CERT. "MEM31-C: Free dynamically allocated memory when no longer needed." https://wiki.sei.cmu.edu/confluence/display/c/MEM31-C.+Free+dynamically+allocated+memory+when+no+longer+needed