Use of Blocking Code in Single-threaded, Non-blocking Context
Description
Use of Blocking Code in Single-threaded, Non-blocking Context occurs when the product uses a non-blocking model that relies on a single threaded process for features such as scalability, but it contains code that can block when it is invoked. Single-threaded non-blocking models (Python asyncio, Vert.x, Node.js) are designed to overcome resource constraints of multi-threaded approaches, but blocking code halts the event loop, compromising the model's intended benefits and potentially causing denial of service.
Risk
Blocking code in non-blocking contexts has severe implications. Event loop blocked. All concurrent operations stalled. Denial of service enabled. Application unresponsive. Scalability benefits lost. Resource starvation. Timeout cascades. User experience degraded. Database connections exhausted. API response times spike. High likelihood when mixing synchronous libraries with async frameworks.
Solution
Replace blocking calls with asynchronous non-blocking alternatives. Offload expensive computations to worker threads (framework-dependent). Decompose expensive computations into smaller, sequential operations that yield control back to the event loop. Audit all library calls for blocking behavior. Use async-compatible database drivers and HTTP clients.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability Blocking calls trigger infinite loops or extended iterations, causing indefinite pauses and denial of service through resource consumption. |
Example Code
Vulnerable Code
// Vulnerable: Node.js with blocking operations
const express = require('express');
const fs = require('fs');
const crypto = require('crypto');
const app = express();
// VULNERABLE: Synchronous file read blocks the event loop
app.get('/file/:name', (req, res) => {
try {
// VULNERABLE: fs.readFileSync blocks until complete
const content = fs.readFileSync(`/data/${req.params.name}`);
res.send(content);
} catch (err) {
res.status(500).send('Error');
}
// While this reads a large file, ALL other requests wait
});
// VULNERABLE: CPU-intensive operation blocks event loop
app.get('/hash', (req, res) => {
const data = req.query.data || '';
// VULNERABLE: Expensive computation blocks
// 100,000 iterations of SHA-256
let hash = data;
for (let i = 0; i < 100000; i++) {
hash = crypto.createHash('sha256').update(hash).digest('hex');
}
res.send(hash);
// All other requests blocked during computation
});
// VULNERABLE: Synchronous database-like operation
app.get('/search', (req, res) => {
const results = [];
const items = loadAllItems(); // Assume this returns millions of items
// VULNERABLE: Long-running synchronous loop
for (const item of items) {
if (item.name.includes(req.query.q)) {
results.push(item);
}
}
res.json(results);
// Event loop blocked for entire search
});
// VULNERABLE: Blocking network call
const request = require('sync-request'); // Synchronous HTTP!
app.get('/proxy', (req, res) => {
// VULNERABLE: Synchronous HTTP request
const response = request('GET', req.query.url);
res.send(response.body);
// Blocks until external request completes
});
app.listen(3000);
// Attack: Send requests to /hash endpoint
// Each request blocks all others for seconds
// Few concurrent attackers = complete DoS
# Vulnerable: Python asyncio with blocking code
import asyncio
import time
import hashlib
import requests # Blocking HTTP library!
# VULNERABLE: Blocking function in async context
async def vulnerable_hash_data(data):
# VULNERABLE: CPU-intensive blocking operation
hash_result = data
for i in range(100000):
hash_result = hashlib.sha256(hash_result.encode()).hexdigest()
return hash_result
# This blocks the entire event loop!
# VULNERABLE: Using blocking HTTP library
async def vulnerable_fetch_url(url):
# VULNERABLE: requests.get is blocking!
response = requests.get(url) # Blocks event loop
return response.text
# VULNERABLE: Blocking file I/O
async def vulnerable_read_file(path):
# VULNERABLE: Standard open() is blocking
with open(path, 'r') as f:
return f.read() # Blocks on disk I/O
# VULNERABLE: time.sleep is blocking
async def vulnerable_delay(seconds):
# VULNERABLE: time.sleep blocks the event loop
time.sleep(seconds) # Should use asyncio.sleep!
async def handle_request(data):
# Even though this is async, it blocks
result = await vulnerable_hash_data(data)
return result
async def main():
# These will NOT run concurrently!
# Each blocking call stalls everything
tasks = [
handle_request("data1"),
handle_request("data2"),
handle_request("data3"),
]
results = await asyncio.gather(*tasks)
# Takes 3x as long as expected because each blocks
// Vulnerable: Vert.x with blocking code
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import java.io.*;
import java.net.*;
public class VulnerableVerticle extends AbstractVerticle {
@Override
public void start() {
vertx.createHttpServer()
.requestHandler(req -> {
String path = req.path();
if (path.equals("/file")) {
// VULNERABLE: Blocking file read
try {
// FileInputStream is blocking!
FileInputStream fis = new FileInputStream("/data/large.txt");
byte[] data = fis.readAllBytes(); // Blocks!
fis.close();
req.response().end(new String(data));
} catch (IOException e) {
req.response().setStatusCode(500).end();
}
}
if (path.equals("/external")) {
// VULNERABLE: Blocking HTTP request
try {
URL url = new URL(req.getParam("url"));
// URLConnection is blocking!
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream())
);
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line); // Blocks on each read!
}
req.response().end(response.toString());
} catch (Exception e) {
req.response().setStatusCode(500).end();
}
}
if (path.equals("/compute")) {
// VULNERABLE: CPU-intensive blocking computation
long result = 0;
for (long i = 0; i < 10000000000L; i++) {
result += Math.sqrt(i); // Blocks event loop!
}
req.response().end(String.valueOf(result));
}
})
.listen(8080);
}
}
Fixed Code
// Fixed: Node.js with non-blocking operations
const express = require('express');
const fs = require('fs').promises; // FIXED: Promise-based API
const crypto = require('crypto');
const { Worker } = require('worker_threads');
const app = express();
// FIXED: Asynchronous file read
app.get('/file/:name', async (req, res) => {
try {
// FIXED: fs.promises.readFile is non-blocking
const content = await fs.readFile(`/data/${req.params.name}`);
res.send(content);
} catch (err) {
res.status(500).send('Error');
}
});
// FIXED: CPU-intensive work offloaded to worker thread
app.get('/hash', async (req, res) => {
const data = req.query.data || '';
try {
// FIXED: Use worker thread for CPU-intensive work
const hash = await runInWorker(data);
res.send(hash);
} catch (err) {
res.status(500).send('Error');
}
});
function runInWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker(`
const { parentPort, workerData } = require('worker_threads');
const crypto = require('crypto');
let hash = workerData;
for (let i = 0; i < 100000; i++) {
hash = crypto.createHash('sha256').update(hash).digest('hex');
}
parentPort.postMessage(hash);
`, { eval: true, workerData: data });
worker.on('message', resolve);
worker.on('error', reject);
});
}
// FIXED: Chunked processing for large datasets
app.get('/search', async (req, res) => {
const items = await loadAllItemsAsync();
const query = req.query.q;
const results = [];
// FIXED: Process in chunks, yielding to event loop
const CHUNK_SIZE = 1000;
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
for (const item of chunk) {
if (item.name.includes(query)) {
results.push(item);
}
}
// FIXED: Yield to event loop after each chunk
await setImmediatePromise();
}
res.json(results);
});
function setImmediatePromise() {
return new Promise(resolve => setImmediate(resolve));
}
// FIXED: Non-blocking HTTP client
const axios = require('axios'); // Async HTTP library
app.get('/proxy', async (req, res) => {
try {
// FIXED: Async HTTP request
const response = await axios.get(req.query.url, {
timeout: 5000 // Also add timeout!
});
res.send(response.data);
} catch (err) {
res.status(500).send('Error');
}
});
app.listen(3000);
# Fixed: Python asyncio with non-blocking code
import asyncio
import hashlib
import aiohttp # FIXED: Async HTTP library
import aiofiles # FIXED: Async file library
from concurrent.futures import ProcessPoolExecutor
# FIXED: CPU-intensive work in process pool
executor = ProcessPoolExecutor(max_workers=4)
def cpu_intensive_hash(data):
"""Run in separate process to avoid blocking event loop."""
hash_result = data
for i in range(100000):
hash_result = hashlib.sha256(hash_result.encode()).hexdigest()
return hash_result
async def secure_hash_data(data):
# FIXED: Run CPU-intensive work in process pool
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(executor, cpu_intensive_hash, data)
return result
# FIXED: Using async HTTP library
async def secure_fetch_url(url):
# FIXED: aiohttp is non-blocking
async with aiohttp.ClientSession() as session:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as response:
return await response.text()
# FIXED: Using async file I/O
async def secure_read_file(path):
# FIXED: aiofiles for non-blocking file I/O
async with aiofiles.open(path, 'r') as f:
return await f.read()
# FIXED: Use asyncio.sleep instead of time.sleep
async def secure_delay(seconds):
# FIXED: asyncio.sleep yields to event loop
await asyncio.sleep(seconds)
# FIXED: Chunked processing for large data
async def secure_process_large_list(items, query):
results = []
CHUNK_SIZE = 1000
for i in range(0, len(items), CHUNK_SIZE):
chunk = items[i:i + CHUNK_SIZE]
for item in chunk:
if query in item.get('name', ''):
results.append(item)
# FIXED: Yield to event loop
await asyncio.sleep(0)
return results
async def handle_request(data):
# FIXED: Now truly concurrent
result = await secure_hash_data(data)
return result
async def main():
# FIXED: These now run concurrently
tasks = [
handle_request("data1"),
handle_request("data2"),
handle_request("data3"),
]
results = await asyncio.gather(*tasks)
# Runs in parallel using process pool
asyncio.run(main())
// Fixed: Vert.x with non-blocking code
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.web.client.WebClient;
public class SecureVerticle extends AbstractVerticle {
private WebClient webClient;
@Override
public void start() {
webClient = WebClient.create(vertx);
vertx.createHttpServer()
.requestHandler(req -> {
String path = req.path();
if (path.equals("/file")) {
// FIXED: Non-blocking file read
vertx.fileSystem().readFile("/data/large.txt", result -> {
if (result.succeeded()) {
req.response().end(result.result());
} else {
req.response().setStatusCode(500).end();
}
});
}
if (path.equals("/external")) {
// FIXED: Non-blocking HTTP request
String url = req.getParam("url");
webClient.getAbs(url)
.timeout(5000)
.send(result -> {
if (result.succeeded()) {
req.response().end(result.result().bodyAsString());
} else {
req.response().setStatusCode(500).end();
}
});
}
if (path.equals("/compute")) {
// FIXED: CPU-intensive work on worker thread
vertx.executeBlocking(promise -> {
// This runs on worker pool, not event loop
long result = 0;
for (long i = 0; i < 10000000000L; i++) {
result += Math.sqrt(i);
}
promise.complete(result);
}, result -> {
if (result.succeeded()) {
req.response().end(String.valueOf(result.result()));
} else {
req.response().setStatusCode(500).end();
}
});
}
})
.listen(8080);
}
}
CVE Examples
- CVE-2020-8203: Node.js application DoS through blocking operations in event loop.
- CVE-2019-10790: Event loop blocking in Express.js middleware caused denial of service.
Related CWEs
- CWE-834: Excessive Iteration (parent)
- CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop') (can follow)
- CWE-557: Concurrency Issues (category)
- CAPEC-25: Forced Deadlock
References
- 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"
- Python. "asyncio - Asynchronous I/O"
- Vert.x. "Golden Rule - Don't Block the Event Loop"