Verwendung von blockierendem Code in nicht-blockierendem Aufrufkontext
Beschreibung
Verwendung von blockierendem Code in nicht-blockierendem Aufrufkontext tritt auf, wenn ein Produkt eine Funktion verwendet, die einen langlaufenden oder blockierenden Aufruf durchführt, in einem Kontext, der für asynchrone, nicht-blockierende Operationen konzipiert ist. Dies ist besonders problematisch in ereignisgesteuerten Architekturen wie Node.js-Event-Loops, GUI-Haupt-Threads oder asynchronen Serveranwendungen, wo blockierende Operationen die gesamte Anwendung einfrieren und verhindern können, dass andere Anfragen oder Ereignisse verarbeitet werden, was zu Leistungsverschlechterung oder Denial of Service führt.
Risiko
Blockierender Code in nicht-blockierenden Kontexten erzeugt verschiedene Risiken. Er kann einen gesamten Server unresponsiv machen und einen effektiven Denial-of-Service-Zustand schaffen. In Node.js-Anwendungen friert das Blockieren des Event-Loops alle gleichzeitigen Anfragen ein. Blockierendes I/O in GUI-Threads verursacht Anwendungseinfrieren. Synchrone Datenbankabfragen oder Dateioperationen in asynchronem Code können unter Last zu kaskadierenden Ausfällen führen.
Lösung
Verwenden Sie asynchrone Alternativen für alle I/O-Operationen in nicht-blockierenden Kontexten. Verlagern Sie CPU-intensive Arbeit in Worker-Threads oder separate Prozesse. Implementieren Sie ordnungsgemäße async/await-Muster. Verwenden Sie nicht-blockierende Bibliotheken für Datenbankoperationen. Überwachen Sie Event-Loop-Verzögerung und Blockierungsmetriken. Führen Sie Codereviews durch, um synchrone Aufrufe in asynchronen Kontexten zu erkennen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Bereich: Verfügbarkeit Blockierende Aufrufe können alle gleichzeitigen Anfragen zum Stillstand bringen und effektiv einen Denial of Service verursachen. |
| Sonstiges | Bereich: Sonstige Qualitätsverschlechterung - Leistungsprobleme können Benutzererfahrung und Anwendungsreaktionsfähigkeit beeinträchtigen. |
Beispielcode und Lösung
Verwundbarer Code
// Verwundbar: Blockierende Operationen in Node.js Event-Loop
const fs = require('fs');
const crypto = require('crypto');
const express = require('express');
const app = express();
// VERWUNDBAR: Synchrones Dateilesen blockiert den Event-Loop
app.get('/datei/:name', (req, res) => {
// Dies blockiert ALLE anderen Anfragen während des Lesens
const inhalt = fs.readFileSync(`/daten/${req.params.name}`);
res.send(inhalt);
});
// VERWUNDBAR: CPU-intensive Operation blockiert den Event-Loop
app.post('/hash', (req, res) => {
const { passwort } = req.body;
// Synchrones Hashing blockiert für ~100ms
const hash = crypto.pbkdf2Sync(passwort, 'salz', 100000, 64, 'sha512');
res.json({ hash: hash.toString('hex') });
});
// VERWUNDBAR: Synchrone Datenbankoperation
app.get('/benutzer/:id', async (req, res) => {
// Obwohl markiert als async, ist diese Bibliothek tatsächlich synchron
const benutzer = synchroneDB.abfrage(`SELECT * FROM benutzer WHERE id = ${req.params.id}`);
res.json(benutzer);
});
// VERWUNDBAR: Große synchrone Verarbeitung
app.post('/verarbeiten', (req, res) => {
const daten = req.body.items;
// Verarbeitung größer Datensätze blockiert den Event-Loop
const ergebnisse = [];
for (let i = 0; i < daten.length; i++) {
// Komplexe synchrone Berechnungen
ergebnisse.push(komplexeBerechnung(daten[i]));
}
res.json(ergebnisse);
});
# Verwundbar: Blockierender Code in asyncio
import asyncio
import time
import requests
async def verwundbarer_handler(anfrage):
# VERWUNDBAR: Synchrones HTTP blockiert die Event-Loop
antwort = requests.get('https://api.beispiel.de/daten') # Blockiert!
# VERWUNDBAR: time.sleep blockiert die Event-Loop
time.sleep(5) # Blockiert alle anderen Coroutinen!
return antwort.json()
# VERWUNDBAR: Synchrones Dateilesen in async-Kontext
async def verwundbar_datei_lesen(pfad):
# Dies blockiert die Event-Loop
with open(pfad, 'r') as f:
return f.read() # Blockiert!
# VERWUNDBAR: CPU-intensive Arbeit in async-Funktion
async def verwundbare_berechnung(daten):
# CPU-gebundene Arbeit blockiert die Event-Loop
ergebnis = 0
for i in range(10000000):
ergebnis += i * daten # Blockiert!
return ergebnis
Sichere Lösung
// Sicher: Asynchrone Operationen in Node.js
const fs = require('fs').promises;
const crypto = require('crypto');
const { Worker } = require('worker_threads');
const express = require('express');
const app = express();
// Sicher: Asynchrones Dateilesen
app.get('/datei/:name', async (req, res) => {
try {
// Nicht-blockierendes Dateilesen
const inhalt = await fs.readFile(`/daten/${req.params.name}`);
res.send(inhalt);
} catch (fehler) {
res.status(404).send('Datei nicht gefunden');
}
});
// Sicher: Promisifiziertes Hashing
app.post('/hash', async (req, res) => {
const { passwort } = req.body;
// Nicht-blockierendes asynchrones Hashing
crypto.pbkdf2(passwort, 'salz', 100000, 64, 'sha512', (err, hash) => {
if (err) {
res.status(500).send('Hash-Fehler');
return;
}
res.json({ hash: hash.toString('hex') });
});
});
// Sicher: Worker-Thread für CPU-intensive Arbeit
app.post('/verarbeiten', async (req, res) => {
const daten = req.body.items;
// Arbeit an Worker-Thread auslagern
const worker = new Worker('./worker.js', {
workerData: daten
});
worker.on('message', (ergebnis) => {
res.json(ergebnis);
});
worker.on('error', (fehler) => {
res.status(500).send('Verarbeitungsfehler');
});
});
// Sicher: Stapelverarbeitung um Blockierung zu vermeiden
async function verarbeiteStapel(items, stapelGröße = 100) {
const ergebnisse = [];
for (let i = 0; i < items.length; i += stapelGröße) {
const stapel = items.slice(i, i + stapelGröße);
// Verarbeite Stapel
const stapelErgebnisse = stapel.map(item => verarbeiteItem(item));
ergebnisse.push(...stapelErgebnisse);
// Yield zur Event-Loop zwischen Stapeln
await new Promise(resolve => setImmediate(resolve));
}
return ergebnisse;
}
# Sicher: Richtige async/await-Nutzung in Python
import asyncio
import aiohttp
import aiofiles
from concurrent.futures import ProcessPoolExecutor
async def sicherer_handler(anfrage):
# Sicher: Async HTTP-Client verwenden
async with aiohttp.ClientSession() as session:
async with session.get('https://api.beispiel.de/daten') as antwort:
return await antwort.json()
# Sicher: Asynchrones Sleep
async def sichere_verzögerung():
await asyncio.sleep(5) # Blockiert andere Coroutinen nicht
# Sicher: Asynchrones Dateilesen
async def sichere_datei_lesen(pfad):
async with aiofiles.open(pfad, 'r') as f:
return await f.read()
# Sicher: CPU-intensive Arbeit an Prozesspool auslagern
async def sichere_berechnung(daten):
loop = asyncio.get_event_loop()
# An Prozesspool-Executor auslagern
with ProcessPoolExecutor() as pool:
ergebnis = await loop.run_in_executor(pool, cpu_intensive_funktion, daten)
return ergebnis
def cpu_intensive_funktion(daten):
# Diese läuft in separatem Prozess
ergebnis = 0
for i in range(10000000):
ergebnis += i * daten
return ergebnis
Ausgenutzt in der Praxis
Node.js Event-Loop-Blockierung in Produktionssystemen
Mehrere hochkarätige Produktionsausfälle wurden durch synchrone Dateioperationen in Node.js-Anwendungen verursacht, die unter Last ausfielen, wenn der Event-Loop blockiert wurde, was Tausende von Benutzern betraf.
ReDoS über Event-Loop-Blockierung
Angreifer haben CPU-intensive reguläre Ausdrücke in Node.js-Servern ausgenutzt, um den Event-Loop zu blockieren und Denial-of-Service-Zustände durch sorgfältig gestaltete Eingaben zu verursachen.
Tools zum Testen und Ausnutzen
-
clinic.js - Leistungsprofiling für Node.js, einschließlich Event-Loop-Blockierungserkennung.
-
blocked-at - Erkennt, wann die Node.js-Event-Loop blockiert wird.
-
0x - Flamegraph-Profiler für Node.js.
CVE-Beispiele
-
CVE-2021-23343 - ReDoS in path-parse verursacht Event-Loop-Blockierung.
-
CVE-2018-16487 - Lodash-ReDoS ermöglicht Event-Loop-Blockierung.
Referenzen
-
MITRE Corporation. "CWE-1322: Use of Blocking Code in Single-threaded, Non-blocking Context." https://cwe.mitre.org/data/definitions/1322.html
-
Node.js. "Don't Block the Event Loop." https://nodejs.org/en/docs/guides/dont-block-the-event-loop/
-
Python asyncio. "Developing with asyncio." https://docs.python.org/3/library/asyncio-dev.html