Allokation von Dateideskriptoren oder Handles ohne Limits oder Drosselung
Beschreibung
Allokation von Dateideskriptoren oder Handles ohne Limits oder Drosselung ist eine Ressourcenverwaltungsschwachstelle, bei der Software Dateideskriptoren oder Handles alloziert, ohne Einschränkungen für die allozierte Menge durchzusetzen. Wenn Anwendungen Dateien, Sockets, Pipes oder andere Systemressourcen ohne Limit-Prüfung öffnen, können Angreifer übermäßige Allokation auslösen, um verfügbare Deskriptoren zu erschöpfen. Dies verursacht Denial of Service, da die Anwendung und potenziell andere Systemprozesse keine Dateioperationen mehr durchführen, keine Netzwerkverbindungen herstellen oder keine anderen deskriptorbasierten Ressourcen mehr nutzen können.
Risiko
Dateideskriptoren sind eine endliche Systemressource, typischerweise pro Prozess und systemweit begrenzt. Wenn Anwendungen Deskriptor-Allokation nicht begrenzen, können Angreifer Ressourcenerschöpfung auf verschiedene Arten verursachen: viele Verbindungen zu einem Server öffnen, Codepfade auslösen, die wiederholt Dateien öffnen, oder Funktionalität ausnutzen, die Deskriptoren erstellt. Sobald erschöpft, kann die Anwendung keine neuen Dateien öffnen, keine Netzwerkverbindungen akzeptieren oder keine I/O-Operationen durchführen, die Deskriptoren erfordern.
Lösung
Implementieren Sie Limits für Dateideskriptor-Allokation auf Anwendungs- und OS-Ebene. Verwenden Sie setrlimit() und getrlimit() auf POSIX-Systemen, um Pro-Prozess-Limits zu setzen. Verfolgen Sie aktive Deskriptoren und verweigern Sie neue Allokationen bei Annäherung an Limits. Implementieren Sie Verbindungsdrosselung für Netzwerkserver. Verwenden Sie Ressourcenpools mit begrenzten Größen. Setzen Sie Timeout-Werte, um inaktive Verbindungen automatisch zu schließen. Überwachen Sie Deskriptornutzung und implementieren Sie Alarme.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Ressourcenverbrauch - Unbegrenzte Deskriptor-Allokation ermöglicht Angreifern die Erschöpfung verfügbarer Dateideskriptoren. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Systemauswirkung - Dateideskriptor-Erschöpfung kann andere Prozesse auf dem System beeinträchtigen. |
Beispielcode
Verwundbarer Code
// Verwundbar: Kein Limit für gleichzeitige Verbindungen
#include <sys/socket.h>
#include <netinet/in.h>
void vulnerable_server(int port) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, SOMAXCONN);
// Verwundbar: Kein Limit für akzeptierte Verbindungen
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd >= 0) {
handleClient(client_fd);
}
}
}
# Verwundbar: Kein Limit für Datei-Handles
class VulnerableFileCache:
def __init__(self):
self.open_files = {}
def get_file(self, filename):
if filename not in self.open_files:
# Verwundbar: Kein Limit für Cache-Größe
self.open_files[filename] = open(filename, 'r')
return self.open_files[filename]
# Angreifer kann unbegrenzt Dateien cachen
Gefixter Code
// Gefixt: Gleichzeitige Verbindungen begrenzen
#include <sys/socket.h>
#include <sys/resource.h>
#define MAX_CONNECTIONS 1000
static int current_connections = 0;
static pthread_mutex_t conn_mutex = PTHREAD_MUTEX_INITIALIZER;
void fixed_server(int port) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// Ressourcenlimits setzen
struct rlimit rl;
rl.rlim_cur = MAX_CONNECTIONS + 10;
rl.rlim_max = MAX_CONNECTIONS + 10;
setrlimit(RLIMIT_NOFILE, &rl);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, SOMAXCONN);
while (1) {
// Gefixt: Limit vor Akzeptieren prüfen
pthread_mutex_lock(&conn_mutex);
int can_accept = (current_connections < MAX_CONNECTIONS);
pthread_mutex_unlock(&conn_mutex);
if (!can_accept) {
sleep(1);
continue;
}
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd >= 0) {
pthread_mutex_lock(&conn_mutex);
current_connections++;
pthread_mutex_unlock(&conn_mutex);
handleClient(client_fd);
}
}
}
# Gefixt: Begrenzter Datei-Cache mit LRU-Eviction
from collections import OrderedDict
class FixedFileCache:
def __init__(self, max_files=100):
self.max_files = max_files
self.open_files = OrderedDict()
def get_file(self, filename):
if filename in self.open_files:
# An Ende verschieben (zuletzt verwendet)
self.open_files.move_to_end(filename)
return self.open_files[filename]
# Gefixt: Ältestes entfernen wenn bei Limit
while len(self.open_files) >= self.max_files:
oldest_name, oldest_file = self.open_files.popitem(last=False)
oldest_file.close()
self.open_files[filename] = open(filename, 'r')
return self.open_files[filename]
def close_all(self):
for f in self.open_files.values():
f.close()
self.open_files.clear()
// Gefixt: Begrenzter Verbindungspool
public class FixedConnectionPool {
private final int maxConnections;
private final BlockingQueue<Connection> availableConnections;
private final Semaphore connectionSemaphore;
public FixedConnectionPool(int maxConnections) {
this.maxConnections = maxConnections;
this.availableConnections = new LinkedBlockingQueue<>();
this.connectionSemaphore = new Semaphore(maxConnections);
}
public Connection getConnection(long timeout, TimeUnit unit)
throws SQLException, InterruptedException {
// Gefixt: Blockieren wenn Pool erschöpft
if (!connectionSemaphore.tryAcquire(timeout, unit)) {
throw new SQLException("Verbindungspool erschöpft");
}
Connection conn = availableConnections.poll();
if (conn == null || conn.isClosed()) {
conn = createNewConnection();
}
return conn;
}
public void releaseConnection(Connection conn) {
if (conn != null && !conn.isClosed()) {
availableConnections.offer(conn);
}
connectionSemaphore.release();
}
}
Erkennungsmethoden
- Statische Analyse: SAST-Tools können unbegrenzte Allokationsschleifen und fehlende Limit-Prüfungen identifizieren.
- Laufzeitüberwachung: Dateideskriptor-Nutzung mit lsof, /proc/pid/fd oder benutzerdefinierter Instrumentierung verfolgen.
- Lasttests: Anwendungen unter Stress testen, um Deskriptor-Erschöpfungspunkte zu identifizieren.
Referenzen
- MITRE Corporation. "CWE-774: Allocation of File Descriptors or Handles Without Limits or Throttling." https://cwe.mitre.org/data/definitions/774.html
- CERT C Coding Standard. "FIO42-C. Close files when they are no longer needed."
- Linux Manual. "getrlimit, setrlimit - get/set resource limits."