Unsachgemäße Kontrolle einer Ressource über ihren Lebenszyklus
Beschreibung
Unsachgemäße Kontrolle einer Ressource über ihren Lebenszyklus tritt auf, wenn eine Anwendung den vollständigen Lebenszyklus einer Ressource von der Zuweisung bis zur Freigabe nicht ordnungsgemäß verwaltet. Dies umfasst das Versäumnis, Ressourcen zu initialisieren, die Verwendung von Ressourcen nach ihrer Freigabe, die mehrfache Freigabe von Ressourcen oder das Nie-Freigeben von Ressourcen. Ressourcen können Speicher, Dateihandles, Datenbankverbindungen, Netzwerk-Sockets und andere Systemobjekte umfassen.
Risiko
Speicherlecks verursachen Ressourcenerschöpfung und Denial of Service. Use-after-free-Schwachstellen ermöglichen Codeausführung. Double-free-Schwachstellen korrumpieren Speicherzuweiser. Nicht initialisierte Ressourcen enthalten sensible Daten aus vorherigen Operationen. Ressourcenlecks in langlebigen Diensten verursachen schrittweise Degradierung. Dateideskriptor-Erschöpfung verhindert neue Verbindungen.
Lösung
Befolgen Sie RAII (Resource Acquisition Is Initialization)-Muster. Verwenden Sie Smart Pointer und automatisches Ressourcenmanagement. Implementieren Sie ordnungsgemäße Bereinigung in allen Codepfaden einschließlich Exceptions. Verwenden Sie try-with-resources oder ähnliche Konstrukte. Verfolgen Sie den Ressourcenstatus explizit. Implementieren Sie Ressourcenpools mit ordnungsgemäßem Lebenszyklus-Management.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Ressourcenerschöpfung Geleakte Ressourcen verursachen Dienst-Degradierung. |
| Vertraulichkeit | Bereich: Informationspreisgabe Nicht initialisierte Ressourcen geben sensible Daten preis. |
| Integrität | Bereich: Speicherkorruption Double-free und Use-after-free korrumpieren Speicher. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: Unsachgemäßes Ressourcen-Lebenszyklus-Management
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// VERWUNDBAR: Speicherleck im Fehlerpfad
char* read_file_vulnerable(const char* filename) {
FILE* file = fopen(filename, "r");
if (!file) {
return NULL;
}
char* buffer = malloc(1024);
if (!buffer) {
// VERWUNDBAR: Dateihandle geleakt!
return NULL;
}
if (fread(buffer, 1, 1024, file) == 0) {
// VERWUNDBAR: Datei und Buffer geleakt!
return NULL;
}
fclose(file);
return buffer; // Aufrufer muss freigeben, aber kein Hinweis darauf
}
// VERWUNDBAR: Use-after-free
void use_after_free_vulnerable() {
char* data = malloc(100);
strcpy(data, "sensitive");
free(data);
// VERWUNDBAR: Verwendet freigegebenen Speicher
printf("Data: %s\n", data); // Use-after-free!
}
// VERWUNDBAR: Double-free
void double_free_vulnerable(int condition) {
char* ptr = malloc(100);
if (condition) {
free(ptr);
}
// VERWUNDBAR: Kann bereits freigegebenen Speicher freigeben
free(ptr); // Double-free wenn condition wahr war!
}
// VERWUNDBAR: Verwendung von nicht initialisiertem Speicher
void uninitialized_memory_vulnerable() {
char buffer[256]; // VERWUNDBAR: Nicht initialisiert
// VERWUNDBAR: Liest nicht initialisierte Daten
if (buffer[0] == 'A') { // Undefiniertes Verhalten
do_something();
}
// VERWUNDBAR: Sendet nicht initialisierte Daten
send(socket, buffer, sizeof(buffer), 0); // Leakt Stack-Daten
}
// VERWUNDBAR: Ressource wird nie freigegeben
void resource_leak_vulnerable() {
while (1) {
int* data = malloc(sizeof(int) * 1000);
// VERWUNDBAR: Wird nie freigegeben - Speichererschöpfung
process_data(data);
}
}
// VERWUNDBAR: Dateideskriptor-Leck
void fd_leak_vulnerable(const char** filenames, int count) {
for (int i = 0; i < count; i++) {
int fd = open(filenames[i], O_RDONLY);
if (fd < 0) continue;
process_file(fd);
// VERWUNDBAR: fd wird nie geschlossen - Deskriptor-Erschöpfung
}
}
# VERWUNDBAR: Python-Ressourcen-Lebenszyklus-Probleme
class VulnerableResourceManager:
def __init__(self):
self.connection = None
def connect(self):
# VERWUNDBAR: Alte Verbindung nicht geschlossen vor Erstellung einer neuen
self.connection = create_database_connection()
def execute(self, query):
# VERWUNDBAR: Keine Prüfung ob Verbindung existiert
return self.connection.execute(query)
# VERWUNDBAR: Keine automatisch aufgerufene Bereinigungsmethode
# VERWUNDBAR: Dateihandle-Lecks
def read_files_vulnerable(filenames):
results = []
for filename in filenames:
f = open(filename, 'r')
# VERWUNDBAR: Dateien werden nie geschlossen
results.append(f.read())
return results
# VERWUNDBAR: Zirkulare Referenzen verursachen Speicherlecks
class Node:
def __init__(self, data):
self.data = data
self.next = None
def create_cycle_vulnerable():
a = Node("A")
b = Node("B")
# VERWUNDBAR: Zirkulare Referenz - potenzielles Speicherleck
a.next = b
b.next = a
# Wenn Funktion zurückkehrt, kann zirkulare Referenz Sammlung verhindern
# VERWUNDBAR: Exception verursacht Ressourcenleck
def process_file_vulnerable(filename):
f = open(filename, 'r')
data = f.read()
process(data) # Wenn dies eine Exception auslöst, wird Datei nie geschlossen!
f.close()
// VERWUNDBAR: Java-Ressourcen-Lebenszyklus-Probleme
public class VulnerableResourceManagement {
// VERWUNDBAR: Verbindungsleck bei Exception
public String queryDatabase(String sql) throws SQLException {
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
// VERWUNDBAR: Bei Exception werden conn und stmt nie geschlossen
ResultSet rs = stmt.executeQuery(sql);
String result = rs.getString(1);
conn.close(); // Nur erreicht wenn keine Exception
return result;
}
// VERWUNDBAR: Stream nicht geschlossen
public String readFile(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] data = new byte[1024];
fis.read(data);
// VERWUNDBAR: fis wird nie geschlossen
return new String(data);
}
// VERWUNDBAR: Lock wird nie freigegeben
private final ReentrantLock lock = new ReentrantLock();
public void processWithLock() {
lock.lock();
doProcessing(); // Bei Exception wird Lock nie freigegeben!
lock.unlock();
}
// VERWUNDBAR: Thread-Pool wird nie heruntergefahren
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void submitTasks(List<Runnable> tasks) {
for (Runnable task : tasks) {
executor.submit(task);
}
// VERWUNDBAR: executor.shutdown() wird nie aufgerufen
}
}
// VERWUNDBAR: Node.js-Ressourcen-Lebenszyklus-Probleme
class VulnerableResourceManager {
// VERWUNDBAR: Event-Listener-Leck
setupListeners() {
// VERWUNDBAR: Listener akkumulieren, werden nie entfernt
this.emitter.on('data', this.handleData);
this.emitter.on('error', this.handleError);
// Mehrfach ohne Bereinigung aufgerufen = Speicherleck
}
// VERWUNDBAR: Timer-Leck
startPolling() {
// VERWUNDBAR: Intervall wird nie gelöscht
setInterval(() => {
this.poll();
}, 1000);
}
// VERWUNDBAR: Stream nicht ordnungsgemäß gehandhabt
async readFile(path) {
const stream = fs.createReadStream(path);
const chunks = [];
// VERWUNDBAR: Bei Fehler wird Stream möglicherweise nicht geschlossen
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
// VERWUNDBAR: Datenbankverbindungs-Leck
async query(sql) {
const client = await pool.connect();
// VERWUNDBAR: Wenn Abfrage Exception wirft, wird Client nie freigegeben
const result = await client.query(sql);
client.release();
return result;
}
// VERWUNDBAR: Kind-Prozess-Leck
spawnProcess(command) {
const child = spawn(command);
// VERWUNDBAR: Kind-Prozess wird beim Herunterfahren nie beendet
return child;
}
}
Sichere Lösung
// SICHER: Ordnungsgemäßes Ressourcen-Lebenszyklus-Management
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// SICHER: Ordnungsgemäße Bereinigung in allen Pfaden
char* read_file_safe(const char* filename) {
FILE* file = NULL;
char* buffer = NULL;
file = fopen(filename, "r");
if (!file) {
goto cleanup;
}
buffer = malloc(1024);
if (!buffer) {
goto cleanup;
}
if (fread(buffer, 1, 1024, file) == 0) {
free(buffer);
buffer = NULL;
goto cleanup;
}
cleanup:
if (file) {
fclose(file);
}
return buffer; // Aufrufer muss Nicht-NULL-Rückgabe freigeben
}
// SICHER: Pointer nach free bereinigen
void safe_free_pattern() {
char* data = malloc(100);
if (!data) return;
strcpy(data, "sensitive");
// SICHER: Bereinigen und nullen
memset(data, 0, 100); // Sensible Daten löschen
free(data);
data = NULL; // Use-after-free verhindern
// Sicher: NULL-Prüfung verhindert Probleme
if (data) {
printf("Data: %s\n", data);
}
}
// SICHER: Zustand verfolgen um Double-free zu verhindern
typedef struct {
char* data;
int freed;
} ManagedBuffer;
void safe_release(ManagedBuffer* buf) {
// SICHER: Zustand vor Freigabe prüfen
if (buf && buf->data && !buf->freed) {
free(buf->data);
buf->data = NULL;
buf->freed = 1;
}
}
// SICHER: Gesamten Speicher initialisieren
void initialized_memory_safe() {
char buffer[256] = {0}; // SICHER: Null-initialisiert
// Oder memset verwenden
char buffer2[256];
memset(buffer2, 0, sizeof(buffer2));
// Oder calloc für Heap
char* heap_buf = calloc(256, 1); // SICHER: Null-initialisiert
if (heap_buf) {
use_buffer(heap_buf);
free(heap_buf);
}
}
// SICHER: Ressourcen immer freigeben
void resource_managed_safe() {
int* data = NULL;
while (should_continue()) {
data = malloc(sizeof(int) * 1000);
if (!data) break;
process_data(data);
// SICHER: Nach jeder Verwendung freigeben
free(data);
data = NULL;
}
}
// SICHER: Dateideskriptoren schließen
void fd_managed_safe(const char** filenames, int count) {
for (int i = 0; i < count; i++) {
int fd = open(filenames[i], O_RDONLY);
if (fd < 0) continue;
process_file(fd);
// SICHER: Immer schließen
close(fd);
}
}
# SICHER: Python-Ressourcen-Lebenszyklus-Management
class SafeResourceManager:
def __init__(self):
self.connection = None
def connect(self):
# SICHER: Alte Verbindung schließen bevor neue erstellt wird
if self.connection:
self.connection.close()
self.connection = create_database_connection()
def execute(self, query):
if not self.connection:
raise RuntimeError("Not connected")
return self.connection.execute(query)
# SICHER: Context-Manager-Protokoll für automatische Bereinigung
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.connection:
self.connection.close()
self.connection = None
return False
# SICHER: Context-Manager verwenden
def read_files_safe(filenames):
results = []
for filename in filenames:
# SICHER: Context-Manager stellt sicher, dass Datei geschlossen wird
with open(filename, 'r') as f:
results.append(f.read())
return results
# SICHER: weakref verwenden um Zyklen zu brechen
import weakref
class SafeNode:
def __init__(self, data):
self.data = data
self._next = None
@property
def next(self):
return self._next() if self._next else None
@next.setter
def next(self, node):
# SICHER: Schwache Referenz bricht Zyklus
self._next = weakref.ref(node) if node else None
# SICHER: Exception-sichere Dateiverarbeitung
def process_file_safe(filename):
with open(filename, 'r') as f:
data = f.read()
process(data) # Datei wird auch bei Exception geschlossen
// SICHER: Java-Ressourcen-Lebenszyklus mit try-with-resources
public class SafeResourceManagement {
// SICHER: Try-with-resources stellt Bereinigung sicher
public String queryDatabase(String sql) throws SQLException {
try (Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
return rs.getString(1);
// SICHER: Alle Ressourcen werden automatisch geschlossen
}
}
// SICHER: Stream wird automatisch geschlossen
public String readFile(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
byte[] data = new byte[1024];
fis.read(data);
return new String(data);
}
}
// SICHER: Lock wird in finally-Block freigegeben
private final ReentrantLock lock = new ReentrantLock();
public void processWithLock() {
lock.lock();
try {
doProcessing();
} finally {
// SICHER: Wird immer freigegeben
lock.unlock();
}
}
// SICHER: Thread-Pool mit ordnungsgemäßem Herunterfahren
private ExecutorService executor = Executors.newFixedThreadPool(10);
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
// SICHER: Node.js-Ressourcen-Lebenszyklus-Management
class SafeResourceManager {
constructor() {
this.listeners = [];
this.intervals = [];
this.children = [];
}
// SICHER: Listener verfolgen und bereinigen
setupListeners() {
const dataHandler = this.handleData.bind(this);
const errorHandler = this.handleError.bind(this);
this.emitter.on('data', dataHandler);
this.emitter.on('error', errorHandler);
// SICHER: Für Bereinigung verfolgen
this.listeners.push(
{ event: 'data', handler: dataHandler },
{ event: 'error', handler: errorHandler }
);
}
// SICHER: Timer für Bereinigung verfolgen
startPolling() {
const intervalId = setInterval(() => {
this.poll();
}, 1000);
this.intervals.push(intervalId);
return intervalId;
}
// SICHER: Ordnungsgemäße Stream-Behandlung
async readFile(path) {
const stream = fs.createReadStream(path);
const chunks = [];
try {
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
} finally {
// SICHER: Sicherstellen dass Stream zerstört wird
stream.destroy();
}
}
// SICHER: Datenbankverbindung mit ordnungsgemäßer Freigabe
async query(sql) {
const client = await pool.connect();
try {
return await client.query(sql);
} finally {
// SICHER: Wird immer freigegeben
client.release();
}
}
// SICHER: Alle Ressourcen bereinigen
async cleanup() {
// Alle Listener entfernen
for (const { event, handler } of this.listeners) {
this.emitter.off(event, handler);
}
this.listeners = [];
// Alle Intervalle löschen
for (const id of this.intervals) {
clearInterval(id);
}
this.intervals = [];
// Alle Kind-Prozesse beenden
for (const child of this.children) {
child.kill();
}
this.children = [];
}
}
Ausgenutzt in der Praxis
Use-After-Free-Exploits
Speicherkorruption, die Remote-Code-Ausführung ermöglicht.
Ressourcenerschöpfung
Speicherlecks, die Denial of Service verursachen.
Informationspreisgabe
Nicht initialisierter Speicher, der sensible Daten preisgibt.
Tools zum Testen und Ausnutzen
-
Memory-Sanitizer (ASan, MSan).
-
Valgrind zur Speicherleck-Erkennung.
-
Statische Analysatoren für Ressourcen-Lebenszyklen.
CVE-Beispiele
-
CVE-2021-3156: Use-after-free in sudo.
-
CVE-2019-14287: Ressourcenmanagement-Fehler in sudo.
Referenzen
-
MITRE. "CWE-664: Improper Control of a Resource Through its Lifetime." https://cwe.mitre.org/data/definitions/664.html
-
RAII-Muster-Dokumentation.