Fehlende Freigabe von Dateideskriptor oder Handle nach effektiver Lebensdauer
Beschreibung
Fehlende Freigabe von Dateideskriptor oder Handle nach effektiver Lebensdauer ist eine Ressourcenverwaltungsschwachstelle, bei der Software Dateideskriptoren oder Handles nicht schließt, nachdem sie nicht mehr benötigt werden. Im Unterschied zum Verlust einer Referenz auf einen Deskriptor (CWE-773) beinhaltet diese Schwachstelle das Beibehalten der Referenz, aber das einfache Nicht-Aufrufen der close-Operation. Der Deskriptor bleibt offen und verbraucht Systemressourcen. Wenn sich Deskriptoren ansammeln ohne freigegeben zu werden, wird der verfügbare Deskriptor-Pool schließlich erschöpft.
Risiko
Dateideskriptoren sind eine begrenzte Systemressource. Jeder Prozess hat eine maximale Anzahl von Deskriptoren, die er halten kann, und es gibt auch ein systemweites Limit. Wenn Deskriptoren nach der Nutzung nicht freigegeben werden, akkumulieren sie sich im Laufe der Zeit. Bei langlebigen Anwendungen verursachen selbst kleine Lecks schließlich Erschöpfung. Dies führt zur Unfähigkeit, neue Dateien zu öffnen, Netzwerkverbindungen zu akzeptieren, Pipes zu erstellen oder andere I/O-Operationen durchzuführen. Angreifer können die Erschöpfung beschleunigen, indem sie wiederholt Operationen auslösen, die Deskriptoren ohne ordnungsgemäße Bereinigung öffnen.
Lösung
Schließen Sie Dateideskriptoren und Handles immer, wenn sie nicht mehr benötigt werden. Verwenden Sie RAII-Muster in C++ mit Wrapper-Klassen, die Deskriptoren in ihren Destruktoren schließen. In Java verwenden Sie try-with-resources für automatische Bereinigung. Stellen Sie sicher, dass Bereinigung auf allen Codepfaden erfolgt, einschließlich Fehlerpfaden und Exception-Handlern. Verwenden Sie Tools wie Valgrind oder Dateideskriptor-Tracker während der Entwicklung, um Lecks zu identifizieren. Setzen Sie Prozess-Dateideskriptor-Limits mit setrlimit(), um Schäden zu begrenzen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Nicht freigegebene Dateideskriptoren erschöpfen verfügbare Systemressourcen. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Systeminstabilität - Dateideskriptor-Erschöpfung verursacht Anwendungs- und möglicherweise systemweite Ausfälle. |
Beispielcode
Verwundbarer Code
// Verwundbar: Dateideskriptor nicht geschlossen
void vulnerable_read_file(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) return;
char buffer[4096];
ssize_t bytes = read(fd, buffer, sizeof(buffer));
// Verwundbar: fd nie geschlossen
// Buffer verarbeiten...
}
// Verwundbar: Socket nicht nach Verwendung geschlossen
void vulnerable_client_connection(const char* host, int port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return;
struct sockaddr_in addr;
// ... addr einrichten ...
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
send(sockfd, "Hello", 5, 0);
char buffer[100];
recv(sockfd, buffer, sizeof(buffer), 0);
}
// Verwundbar: sockfd nie geschlossen
}
// Verwundbar: Stream nicht geschlossen
public void vulnerableReadFile(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
byte[] buffer = new byte[4096];
int bytesRead = fis.read(buffer);
// Buffer verarbeiten...
// Verwundbar: fis.close() nie aufgerufen
}
// Verwundbar: Close nicht im finally-Block
public void vulnerableWithException(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
processStream(fis); // Kann Exception werfen
fis.close(); // Nicht erreicht wenn Exception geworfen wird
}
Gefixter Code
// Gefixt: Dateideskriptor immer schließen
void fixed_read_file(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) return;
char buffer[4096];
ssize_t bytes = read(fd, buffer, sizeof(buffer));
close(fd); // Gefixt: Immer schließen
// Buffer verarbeiten...
}
// Gefixt: Auf allen Pfaden mit Cleanup-Muster schließen
int fixed_process_file(const char* filename) {
int result = -1;
int fd = -1;
char* buffer = NULL;
fd = open(filename, O_RDONLY);
if (fd < 0) {
goto cleanup;
}
struct stat st;
if (fstat(fd, &st) < 0) {
goto cleanup;
}
buffer = malloc(st.st_size);
if (buffer == NULL) {
goto cleanup;
}
if (read(fd, buffer, st.st_size) < 0) {
goto cleanup;
}
processBuffer(buffer, st.st_size);
result = 0;
cleanup:
free(buffer); // free(NULL) ist sicher
if (fd >= 0) {
close(fd); // Gefixt: Auf allen Pfaden schließen
}
return result;
}
// Gefixt: try-with-resources verwenden (Java 7+)
public void fixedReadFile(String filename) throws IOException {
try (FileInputStream fis = new FileInputStream(filename)) {
byte[] buffer = new byte[4096];
int bytesRead = fis.read(buffer);
// Buffer verarbeiten...
} // Automatisch geschlossen
}
// Gefixt: try-finally verwenden
public void fixedWithFinally(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
try {
processStream(fis);
} finally {
fis.close(); // Immer ausgeführt
}
}
# Gefixt: Context Manager verwenden
def fixed_read_file(filename):
with open(filename, 'r') as f:
content = f.read()
return content # f automatisch geschlossen
# Gefixt: Explizites try-finally
def fixed_process_file(filename):
f = open(filename, 'r')
try:
data = json.load(f)
process(data)
return data
except json.JSONDecodeError:
return None
finally:
f.close() # Schließt immer
CVE-Beispiele
- CVE-2007-0897: Antivirus-Produkt schloss Dateideskriptor nicht bei fehlerhafter Eingabe, was Deskriptor-Erschöpfung und Scan-Ausfälle verursachte.
Erkennungsmethoden
- Statische Analyse: SAST-Tools können fehlende close()-Aufrufe auf Dateideskriptoren identifizieren.
- Laufzeitanalyse: Tools wie Valgrind (memcheck), lsof oder benutzerdefiniertes Tracking erkennen Deskriptor-Lecks.
- Code-Review: Manuell close()-Aufrufe auf allen Codepfaden verifizieren, insbesondere Fehlerpfaden.
Referenzen
- MITRE Corporation. "CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime." https://cwe.mitre.org/data/definitions/775.html
- CERT C Coding Standard. "FIO42-C. Close files when they are no longer needed."
- C++ Core Guidelines. "R.1: Manage resources automatically using resource handles and RAII."