Mehrfache Freigabe derselben Ressource oder desselben Handles
Beschreibung
Mehrfache Freigabe derselben Ressource oder desselben Handles tritt auf, wenn ein Produkt versucht, eine Ressource oder ein Handle mehr als einmal zu schließen oder freizugeben, ohne dass zwischen den Schließvorgängen ein erfolgreicher Öffnungsvorgang stattfindet. Ressourcen wie Speicher, Dateien, Geräte und Socket-Verbindungen erfordern ein "Öffnen" und anschließendes "Schließen" oder "Freigeben". Wenn Entwickler Freigabe-APIs (wie free() in C/C++ oder close()-Operationen) mehr als einmal für dieselbe Ressource aufrufen, werden die Erwartungen der API verletzt, was zu undefiniertem oder unsicherem Verhalten führt, einschließlich Speicherbeschädigung, Datenbeschädigung oder Ausführungspfad-Beschädigung. Die Wiederverwendung von Ressourcen-Identifikatoren kann dazu führen, dass die falsche Ressource geschlossen wird.
Risiko
Mehrfache Freigaben haben schwerwiegende Auswirkungen. Speicherbeschädigung durch Double-Free. Use-after-free-Schwachstellen. Anwendungsabstürze. Beliebige Codeausführung durch Heap-Manipulation. Datenbeschädigung. Ressourcenerschöpfung. Sicherheitsumgehung durch beschädigten Zustand. Schließen der falschen Ressource. Hohe Wahrscheinlichkeit in komplexen Fehlerbehandlungspfaden.
Lösung
Logik vereinfachen, um sicherzustellen, dass Ressourcen nur einmal geschlossen werden, durch Refactoring während der Implementierungsphase. Flags verwenden, um den Ressourcenzustand zu verfolgen -- setzen beim Öffnen, löschen beim Schließen, prüfen vor dem Schließen. Ressourcenvariablen nach dem Schließen auf NULL setzen, da einige APIs Null-Werte ohne Fehler ignorieren. RAII-Muster in C++ verwenden, um Ressourcen-Lebenszyklen automatisch zu verwalten. Ressourcen-Wrapper-Klassen implementieren, die den Zustand verfolgen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit, Integrität Programmfehler, Abstürze oder unvorhersehbares Verhalten durch Ressourcenmanagement-Fehler. |
| Integrität | Bereich: Integrität Speicher- oder Datenbeschädigung durch ungültigen Ressourcenzustand. |
Beispielcode
Verwundbarer Code
// VERWUNDBAR: C-Code mit doppeltem Close/Free
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// VERWUNDBAR: Doppeltes fclose
void vulnerable_file_handling(const char* filename) {
char buffer[1024];
FILE *f = fopen(filename, "r");
if (f) {
fread(buffer, 1, sizeof(buffer) - 1, f);
int r1 = fclose(f); // Erstes Schließen - OK
// ... etwas Verarbeitung ...
int r2 = fclose(f); // VERWUNDBAR: Doppeltes Schließen
// Undefiniertes Verhalten - kann abstürzen oder Speicher beschädigen
}
}
// VERWUNDBAR: Doppeltes Free
void vulnerable_memory_handling(size_t size) {
char* ptr = (char*)malloc(size);
if (ptr == NULL) {
return;
}
// ... ptr verwenden ...
if (some_error_condition) {
free(ptr); // Erstes Free im Fehlerpfad
}
// ... weitere Verarbeitung ...
free(ptr); // VERWUNDBAR: Zweites Free - möglicherweise bereits freigegeben
// Heap-Beschädigung, potenzielle Codeausführung
}
// VERWUNDBAR: Doppeltes Close in der Fehlerbehandlung
int vulnerable_socket_handling(int port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock); // Schließen bei bind-Fehler
return -1;
}
if (listen(sock, 5) < 0) {
close(sock); // Schließen bei listen-Fehler
return -1;
}
// ... Verbindungen akzeptieren ...
close(sock); // Normales Schließen
// Später im Bereinigungscode
close(sock); // VERWUNDBAR: Doppeltes Schließen wenn Fehler aufgetreten ist
return 0;
}
// VERWUNDBAR: Verwirrung durch Wiederverwendung von Ressourcen-Handles
void vulnerable_handle_reuse() {
int fd1 = open("file1.txt", O_RDONLY);
int fd2 = open("file2.txt", O_RDONLY);
// ... Operationen ...
close(fd1); // fd1 schließen
// fd1s Nummer könnte für neue Datei wiederverwendet werden
int fd3 = open("file3.txt", O_RDONLY); // Könnte selbe fd wie fd1 erhalten
// Versehentlich schließen was wir für fd1 halten
close(fd1); // VERWUNDBAR: Schließt tatsächlich fd3!
}
// VERWUNDBAR: Java-Ressourcenbehandlung
import java.io.*;
import java.sql.*;
public class VulnerableResources {
// VERWUNDBAR: Doppeltes Close auf Stream
public void vulnerableStreamHandling(String filename) {
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(filename);
bis = new BufferedInputStream(fis);
// ... Daten lesen ...
bis.close(); // Schließt sowohl bis als auch fis
fis.close(); // VERWUNDBAR: fis bereits durch bis geschlossen
} catch (IOException e) {
try {
if (bis != null) bis.close(); // Kann werfen wenn bereits geschlossen
if (fis != null) fis.close(); // Doppeltes Schließen
} catch (IOException ex) {
// Unterdrückt
}
}
}
// VERWUNDBAR: Doppeltes Schließen der Datenbankverbindung
public void vulnerableDbHandling() throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection("jdbc:...");
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT * FROM users");
// ... Ergebnisse verarbeiten ...
} finally {
// VERWUNDBAR: Kann bei Ausnahme mehrfach schließen
if (rs != null) rs.close();
if (stmt != null) stmt.close(); // Schließt auch rs
if (conn != null) conn.close(); // Schließt auch stmt
// Späterer Bereinigungsversuch
if (rs != null) rs.close(); // VERWUNDBAR: Doppeltes Schließen
}
}
}
# VERWUNDBAR: Python-Ressourcenbehandlung
# VERWUNDBAR: Doppeltes Close auf Datei
def vulnerable_file_handling(filename):
f = open(filename, 'r')
try:
data = f.read()
f.close() # Erstes Schließen
# ... Daten verarbeiten ...
finally:
f.close() # VERWUNDBAR: Doppeltes Schließen
# Kann ValueError auslösen: I/O-Operation auf geschlossener Datei
# VERWUNDBAR: Thread-unsichere Ressourcenfreigabe
import threading
class VulnerableResource:
def __init__(self):
self.handle = allocate_resource()
self.released = False
def release(self):
# VERWUNDBAR: Race Condition erlaubt doppelte Freigabe
if not self.released:
# Thread A und B passieren beide diese Prüfung
free_resource(self.handle) # Beide Threads geben frei
self.released = True
# VERWUNDBAR: Ausnahmebehandlung mit doppelter Freigabe
def vulnerable_multi_resource():
res1 = None
res2 = None
try:
res1 = acquire_resource1()
res2 = acquire_resource2() # Kann werfen
# ... Ressourcen verwenden ...
except Exception as e:
if res1:
release_resource(res1) # Freigabe bei Ausnahme
if res2:
release_resource(res2)
raise
finally:
# VERWUNDBAR: Gibt auch in finally frei
if res1:
release_resource(res1) # Doppelte Freigabe bei Ausnahme
if res2:
release_resource(res2)
Sichere Lösung
// SICHER: Sichere Ressourcenbehandlung in C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// SICHER: Einzelnes Schließen mit NULL-Prüfung
void safe_file_handling(const char* filename) {
char buffer[1024];
FILE *f = fopen(filename, "r");
if (f) {
fread(buffer, 1, sizeof(buffer) - 1, f);
int r = fclose(f);
f = NULL; // SICHER: Nach dem Schließen auf NULL setzen
// ... etwas Verarbeitung ...
// SICHER: Vor dem Schließen prüfen
if (f != NULL) {
fclose(f);
f = NULL;
}
}
}
// SICHER: Speicherbehandlung mit NULL-Muster
void safe_memory_handling(size_t size) {
char* ptr = (char*)malloc(size);
if (ptr == NULL) {
return;
}
// ... ptr verwenden ...
if (some_error_condition) {
free(ptr);
ptr = NULL; // SICHER: Nach Free auf NULL setzen
}
// ... weitere Verarbeitung ...
// SICHER: Vor Free prüfen
if (ptr != NULL) {
free(ptr);
ptr = NULL;
}
}
// SICHER: Flag-basierte Verfolgung verwenden
typedef struct {
FILE* file;
int is_open;
} safe_file_t;
void safe_file_open(safe_file_t* sf, const char* filename, const char* mode) {
sf->file = fopen(filename, mode);
sf->is_open = (sf->file != NULL) ? 1 : 0;
}
void safe_file_close(safe_file_t* sf) {
// SICHER: Flag vor dem Schließen prüfen
if (sf->is_open && sf->file != NULL) {
fclose(sf->file);
sf->file = NULL;
sf->is_open = 0;
}
}
// SICHER: Socket-Behandlung mit einzelnem Schließpunkt
int safe_socket_handling(int port) {
int sock = -1;
int result = -1;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
goto cleanup;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
goto cleanup;
}
if (listen(sock, 5) < 0) {
goto cleanup;
}
// ... Verbindungen akzeptieren ...
result = 0;
cleanup:
// SICHER: Einzelner Schließpunkt
if (sock >= 0) {
close(sock);
sock = -1;
}
return result;
}
// SICHER: RAII-artiger Wrapper in C
typedef struct {
int fd;
int valid;
} safe_fd_t;
safe_fd_t safe_fd_open(const char* path, int flags) {
safe_fd_t sfd;
sfd.fd = open(path, flags);
sfd.valid = (sfd.fd >= 0);
return sfd;
}
void safe_fd_close(safe_fd_t* sfd) {
if (sfd->valid && sfd->fd >= 0) {
close(sfd->fd);
sfd->fd = -1;
sfd->valid = 0;
}
}
// SICHER: Sichere Ressourcenbehandlung in Java
import java.io.*;
import java.sql.*;
public class SafeResources {
// SICHER: try-with-resources verwenden
public void safeStreamHandling(String filename) {
// SICHER: Automatisches Ressourcenmanagement
try (FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis)) {
// ... Daten lesen ...
} catch (IOException e) {
// Ausnahme behandeln - Ressourcen automatisch geschlossen
e.printStackTrace();
}
// Kein manuelles Schließen nötig - garantiert einzelnes Schließen
}
// SICHER: Datenbankverbindung mit try-with-resources
public void safeDbHandling() {
// SICHER: Automatisches Ressourcenmanagement
try (Connection conn = DriverManager.getConnection("jdbc:...");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// ... Ergebnisse verarbeiten ...
} catch (SQLException e) {
e.printStackTrace();
}
// Ressourcen in umgekehrter Reihenfolge geschlossen, genau einmal
}
// SICHER: Benutzerdefiniertes AutoCloseable mit Zustandsverfolgung
public class SafeResource implements AutoCloseable {
private Object handle;
private boolean closed = false;
public SafeResource() {
this.handle = acquireResource();
}
@Override
public synchronized void close() {
// SICHER: Thread-sicheres einzelnes Schließen
if (!closed && handle != null) {
releaseResource(handle);
handle = null;
closed = true;
}
}
public boolean isClosed() {
return closed;
}
}
}
# SICHER: Sichere Ressourcenbehandlung in Python
# SICHER: Kontextmanager verwenden
def safe_file_handling(filename):
# SICHER: Kontextmanager stellt einzelnes Schließen sicher
with open(filename, 'r') as f:
data = f.read()
# ... Daten verarbeiten ...
# Datei automatisch genau einmal geschlossen
# SICHER: Thread-sichere Ressourcenfreigabe
import threading
class SafeResource:
def __init__(self):
self.handle = allocate_resource()
self.released = False
self._lock = threading.Lock()
def release(self):
# SICHER: Thread-sicher mit Lock
with self._lock:
if not self.released:
free_resource(self.handle)
self.handle = None
self.released = True
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release()
return False
# SICHER: Kontextmanager für mehrere Ressourcen verwenden
from contextlib import ExitStack
def safe_multi_resource():
# SICHER: ExitStack behandelt mehrere Ressourcen sicher
with ExitStack() as stack:
res1 = stack.enter_context(acquire_resource1())
res2 = stack.enter_context(acquire_resource2())
# ... Ressourcen verwenden ...
# Beide Ressourcen genau einmal geschlossen, auch bei Ausnahme
# SICHER: Benutzerdefinierter Kontextmanager mit Zustand
class ManagedResource:
def __init__(self):
self._handle = None
self._acquired = False
def __enter__(self):
self._handle = acquire_raw_resource()
self._acquired = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# SICHER: Nur freigeben wenn erworben
if self._acquired and self._handle is not None:
release_raw_resource(self._handle)
self._handle = None
self._acquired = False
return False
# Verwendung
def safe_operation():
with ManagedResource() as res:
# ... res verwenden ...
pass
# Garantiert einzelne Freigabe
CVE-Beispiele
- CVE-2019-5018: SQLite3 Window-Function Double-Free-Schwachstelle, die Codeausführung ermöglicht.
- CVE-2018-14618: curl Double-Free im NTLM-Authentifizierungscode.
- CVE-2017-9047: libxml2 Double-Free bei XML-Validierung.
Verwandte CWEs
- CWE-675: Multiple Operations on Resource in Single-Operation Context (übergeordnet)
- CWE-415: Double Free (untergeordnet)
- CWE-672: Operation on a Resource after Expiration or Release (kann folgen)
- CWE-399: Resource Management Errors (Kategorie)
Referenzen
- MITRE Corporation. "CWE-1341: Multiple Releases of Same Resource or Handle." https://cwe.mitre.org/data/definitions/1341.html
- CERT C. "MEM30-C. Do not access freed memory"
- SEI CERT. "Double-Free Vulnerabilities"