Unsachgemäßes Herunterfahren oder Freigeben von Ressourcen
Beschreibung
Unsachgemäßes Herunterfahren oder Freigeben von Ressourcen tritt auf, wenn Software versäumt, Ressourcen ordnungsgemäß zu schließen, freizugeben oder zu bereinigen, nachdem sie nicht mehr benötigt werden. Dies umfasst Datei-Handles, Datenbankverbindungen, Netzwerk-Sockets, Speicherallokationen, Locks und andere Systemressourcen. Ressourcen können allmählich lecken und zur Erschöpfung führen, oder sie können gesperrt bleiben und Deadlocks verursachen. Unsachgemäße Bereinigung kann auch sensible Daten im Speicher oder in Dateien nach der beabsichtigten Lebensdauer zugänglich lassen.
Risiko
Ressourcenlecks verursachen fortschreitende Verschlechterung der Systemleistung und -stabilität. Datei-Handle-Erschöpfung verhindert das Öffnen neuer Dateien. Verbindungspool-Erschöpfung blockiert Datenbankzugriff. Speicherlecks bringen schließlich Anwendungen oder ganze Systeme zum Absturz. Socket-Lecks führen zu Port-Erschöpfung. Lock-Lecks verursachen Deadlocks. In Sicherheitskontexten können nicht ordnungsgemäß freigegebene Ressourcen sensible Daten zugänglich lassen. Angreifer können Ressourcenerschöpfung für Denial-of-Service ausnutzen, indem sie Codepfade auslösen, die Ressourcen lecken.
Lösung
Implementieren Sie deterministische Ressourcenbereinigung mit try-finally-Blöcken, Kontextmanagern oder RAII-Mustern. Verwenden Sie Verbindungspools mit ordnungsgemäßer Konfiguration. Setzen Sie Timeouts für alle Ressourcenoperationen. Implementieren Sie Ressourcenlimits und Überwachung. Verwenden Sie statische Analyse zur Erkennung von Ressourcenlecks. Verwenden Sie wo möglich speichersichere Sprachen. Löschen Sie sensible Daten bevor Speicher freigegeben wird. Schließen Sie Ressourcen in umgekehrter Reihenfolge der Akquisition. Testen Sie Ressourcenbereinigung unter Fehlerbedingungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Umfang: Ressourcenerschöpfung Geleckte Ressourcen erschöpfen schließlich die Systemkapazität. |
| Vertraulichkeit | Umfang: Datenoffenlegung Sensible Daten können in nicht freigegebenen Ressourcen verbleiben. |
| Stabilität | Umfang: Systemabsturz Fortschreitende Lecks führen zu Out-of-Memory oder ähnlichen Abstürzen. |
Beispielcode
Anfälliger Code
# ANFÄLLIG: Datei-Handle-Leck
def read_config_vulnerable(path):
f = open(path, 'r')
config = json.load(f)
# Datei nie geschlossen!
return config
# ANFÄLLIG: Exception verhindert Cleanup
def process_file_vulnerable(path):
f = open(path, 'r')
data = f.read()
process(data) # Wenn dies Exception wirft, Datei nie geschlossen!
f.close()
# ANFÄLLIG: Datenbankverbindung leckt
import sqlite3
def query_user_vulnerable(user_id):
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id=?", (user_id,))
result = cursor.fetchone()
# Verbindung nie geschlossen!
return result
# ANFÄLLIG: Netzwerk-Socket leckt
import socket
def send_data_vulnerable(host, port, data):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
sock.send(data)
# Socket nie geschlossen!
return True
# ANFÄLLIG: Lock nicht bei Exception freigegeben
import threading
lock = threading.Lock()
def update_shared_vulnerable(value):
lock.acquire()
shared_data['value'] = value
process_update() # Bei Exception, Lock nie freigegeben!
lock.release()
# ANFÄLLIG: Speicher nicht vor Freigabe gelöscht
def process_secret_vulnerable(secret):
key = derive_key(secret)
encrypted = encrypt(data, key)
# Secret und key noch im Speicher wenn Funktion zurückkehrt!
return encrypted
// ANFÄLLIG: Java-Ressourcenlecks
public class VulnerableResources {
// ANFÄLLIG: Stream nicht geschlossen
public String readFileVulnerable(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] data = fis.readAllBytes();
// Stream nie geschlossen!
return new String(data);
}
// ANFÄLLIG: Verbindungsleck
public User getUserVulnerable(int userId) throws SQLException {
Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?");
ps.setInt(1, userId);
ResultSet rs = ps.executeQuery();
// Nichts geschlossen!
if (rs.next()) {
return mapUser(rs);
}
return null;
}
// ANFÄLLIG: Exception-Pfad leckt
public void processDataVulnerable(String path) throws Exception {
FileInputStream fis = new FileInputStream(path);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
processLine(line); // Bei Exception hier, Streams nie geschlossen!
reader.close();
fis.close();
}
// ANFÄLLIG: Lock nicht freigegeben
private final ReentrantLock lock = new ReentrantLock();
public void updateVulnerable(String value) {
lock.lock();
data.setValue(value);
notifyListeners(); // Bei Exception, Lock für immer gehalten!
lock.unlock();
}
}
// ANFÄLLIG: Node.js-Ressourcenlecks
const fs = require('fs');
// ANFÄLLIG: Dateideskriptor leckt
function readFileVulnerable(path) {
return new Promise((resolve, reject) => {
fs.open(path, 'r', (err, fd) => {
if (err) return reject(err);
const buffer = Buffer.alloc(1024);
fs.read(fd, buffer, 0, 1024, 0, (err, bytes) => {
if (err) return reject(err); // fd nicht geschlossen!
resolve(buffer.slice(0, bytes));
// fd auch bei Erfolg nie geschlossen!
});
});
});
}
// ANFÄLLIG: Datenbankverbindung leckt
async function getUserVulnerable(userId) {
const client = new Client();
await client.connect();
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
// Bei Query-Fehler, Verbindung nie geschlossen!
return result.rows[0];
// Auch bei Erfolg leckt Verbindung!
}
// ANFÄLLIG: Event-Listener leckt
class VulnerableComponent {
constructor() {
this.onData = this.handleData.bind(this);
eventEmitter.on('data', this.onData);
// Listener nie entfernt - Speicherleck!
}
handleData(data) {
// Daten verarbeiten...
}
// Keine Cleanup-Methode!
}
// ANFÄLLIG: Interval nicht gelöscht
function startPollingVulnerable() {
const interval = setInterval(() => {
fetchData().then(processData);
}, 1000);
// Interval nie gelöscht - läuft für immer!
return { start: Date.now() };
}
Korrigierter Code
# SICHER: Kontextmanager für automatischen Cleanup
import contextlib
# SICHER: 'with'-Statement verwenden
def read_config_safe(path):
with open(path, 'r') as f:
return json.load(f)
# Datei automatisch geschlossen!
# SICHER: Datenbank mit Kontextmanager
import sqlite3
def query_user_safe(user_id):
with sqlite3.connect('app.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id=?", (user_id,))
return cursor.fetchone()
# Verbindung automatisch geschlossen!
# SICHER: Socket mit Kontextmanager
import socket
def send_data_safe(host, port, data):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
sock.send(data)
return True
# Socket automatisch geschlossen!
# SICHER: Lock mit Kontextmanager
import threading
lock = threading.Lock()
def update_shared_safe(value):
with lock:
shared_data['value'] = value
process_update()
# Lock automatisch freigegeben, auch bei Exception!
# SICHER: try-finally für expliziten Cleanup
def process_multiple_resources_safe():
resource1 = None
resource2 = None
try:
resource1 = acquire_resource1()
resource2 = acquire_resource2()
do_work(resource1, resource2)
finally:
# Immer bereinigen, in umgekehrter Reihenfolge
if resource2:
resource2.close()
if resource1:
resource1.close()
# SICHER: Sensible Daten löschen
import ctypes
def process_secret_safe(secret):
try:
key = derive_key(secret)
encrypted = encrypt(data, key)
return encrypted
finally:
# Sensible Daten aus Speicher löschen
# Für bytes/bytearray:
if hasattr(key, '__iter__'):
for i in range(len(key)):
key[i] = 0
# Für Strings ist dies schwieriger - Secrets nicht als Strings speichern
# SICHER: Benutzerdefinierter Kontextmanager
@contextlib.contextmanager
def managed_connection(url):
conn = create_connection(url)
try:
yield conn
finally:
conn.close()
# Verwendung
def use_connection_safe():
with managed_connection('db://localhost') as conn:
return conn.query('SELECT * FROM users')
// SICHER: Java mit try-with-resources
public class SafeResources {
// SICHER: Try-with-resources für automatisches Schließen
public String readFileSafe(String path) throws IOException {
try (FileInputStream fis = new FileInputStream(path)) {
return new String(fis.readAllBytes());
}
// Stream automatisch geschlossen!
}
// SICHER: Mehrere Ressourcen in try-with-resources
public User getUserSafe(int userId) throws SQLException {
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?")) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
}
// Alle Ressourcen geschlossen!
return null;
}
// SICHER: Lock in try-finally
private final ReentrantLock lock = new ReentrantLock();
public void updateSafe(String value) {
lock.lock();
try {
data.setValue(value);
notifyListeners();
} finally {
lock.unlock(); // Immer freigegeben!
}
}
// SICHER: Verbindungspool-Verwendung
@Autowired
private DataSource dataSource; // Gepoolte Verbindung
public User getUserPooled(int userId) throws SQLException {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id=?")) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return mapUser(rs);
}
}
}
// Verbindung an Pool zurückgegeben!
return null;
}
// SICHER: Sensible Daten löschen
public byte[] processSecretSafe(byte[] secret) {
byte[] key = null;
try {
key = deriveKey(secret);
return encrypt(data, key);
} finally {
// Sensible Daten löschen
if (key != null) {
Arrays.fill(key, (byte) 0);
}
Arrays.fill(secret, (byte) 0);
}
}
}
// SICHER: Benutzerdefiniertes AutoCloseable
public class ManagedResource implements AutoCloseable {
private boolean closed = false;
public void doWork() {
if (closed) throw new IllegalStateException("Ressource geschlossen");
// Arbeit...
}
@Override
public void close() {
if (!closed) {
cleanup();
closed = true;
}
}
}
// Verwendung
try (ManagedResource resource = new ManagedResource()) {
resource.doWork();
}
// SICHER: Node.js mit ordnungsgemäßem Cleanup
const fs = require('fs').promises;
// SICHER: Promises und finally verwenden
async function readFileSafe(path) {
let handle;
try {
handle = await fs.open(path, 'r');
const buffer = Buffer.alloc(1024);
const { bytesRead } = await handle.read(buffer, 0, 1024, 0);
return buffer.slice(0, bytesRead);
} finally {
if (handle) {
await handle.close();
}
}
}
// SICHER: Datenbankverbindung mit Cleanup
async function getUserSafe(userId) {
const client = new Client();
try {
await client.connect();
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows[0];
} finally {
await client.end(); // Immer schließen!
}
}
// SICHER: Verbindungspool verwenden
const { Pool } = require('pg');
const pool = new Pool();
async function getUserPooled(userId) {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
return result.rows[0];
} finally {
client.release(); // An Pool zurückgeben!
}
}
// SICHER: Event-Listener-Cleanup
class SafeComponent {
constructor() {
this.onData = this.handleData.bind(this);
eventEmitter.on('data', this.onData);
}
handleData(data) {
// Daten verarbeiten...
}
destroy() {
// Listener bei Cleanup entfernen!
eventEmitter.off('data', this.onData);
}
}
// SICHER: Interval-Cleanup
function startPollingSafe() {
const interval = setInterval(() => {
fetchData().then(processData);
}, 1000);
return {
start: Date.now(),
stop: () => clearInterval(interval) // Cleanup-Methode!
};
}
// SICHER: AbortController für Cleanup
async function fetchWithTimeout(url, timeout) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
return response.json();
} finally {
clearTimeout(timeoutId); // Timeout immer bereinigen!
}
}
// SICHER: Async-Ressourcen-Cleanup-Muster
class AsyncResource {
constructor() {
this.resource = null;
}
async initialize() {
this.resource = await createResource();
}
async [Symbol.asyncDispose]() {
if (this.resource) {
await this.resource.close();
this.resource = null;
}
}
}
// Verwendung mit expliziter Entsorgung
async function useResourceSafe() {
const resource = new AsyncResource();
try {
await resource.initialize();
// Ressource verwenden...
} finally {
await resource[Symbol.asyncDispose]();
}
}
Ausgenutzt in der Praxis
Ressourcenerschöpfungs-DoS
Anwendungen wurden durch wiederholtes Auslösen von Ressourcenlecks bis zur Erschöpfung der Systemkapazität per DoS angegriffen.
Speicherleck-Informationsoffenlegung
Sensible Daten, die in nicht gelöschtem Speicher verblieben, wurden durch verschiedene Speicheroffenlegungstechniken extrahiert.
Verbindungspool-Erschöpfung
Datenbankverbindungslecks haben Anwendungsausfälle verursacht, wenn Verbindungspools erschöpft wurden.
Tools zum Testen/Ausnutzen
-
Valgrind — Speicher- und Ressourcenleck-Erkennung.
-
Anwendungs-Profiler — Ressourcenverbrauch verfolgen.
-
Statische Analysatoren — Fehlende close/release erkennen.
-
Lasttesttools — Ressourcenerschöpfung auslösen.
CVE-Beispiele
-
CVE-2019-11358 — Ressourcenleck-Probleme.
-
CVE-2021-22096 — Speicherleck in Spring.
-
Zahlreiche anwendungsspezifische Ressourcenleck-Schwachstellen.
Referenzen
-
MITRE. "CWE-404: Improper Resource Shutdown or Release." https://cwe.mitre.org/data/definitions/404.html
-
OWASP. "Resource Management." https://owasp.org/www-community/vulnerabilities/