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

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

Blockierende Aufrufe können alle gleichzeitigen Anfragen zum Stillstand bringen und effektiv einen Denial of Service verursachen.
SonstigesBereich: 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


Referenzen

  1. MITRE Corporation. "CWE-1322: Use of Blocking Code in Single-threaded, Non-blocking Context." https://cwe.mitre.org/data/definitions/1322.html

  2. Node.js. "Don't Block the Event Loop." https://nodejs.org/en/docs/guides/dont-block-the-event-loop/

  3. Python asyncio. "Developing with asyncio." https://docs.python.org/3/library/asyncio-dev.html