Sep 19, 2026·6 min read·5 visits
AnyIO process-pool workers can block indefinitely when writing to undrained stderr streams, causing a permanent deadlock of the calling event loop.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
AnyIO is an asynchronous concurrency framework for Python that abstracts compatibility over either Trio or asyncio. The framework provides utility interfaces to run synchronous execution units inside process pools using the to_process.run_sync() method. This abstraction coordinates execution by establishing inter-process communication (IPC) channels between the parent event loop and the spawned worker process.
During normal operation, the parent process monitors standard streams to read execution outcomes and serialize protocol inputs. The vulnerability, designated as CVE-2026-64847, lies in the handling of the standard error stream (sys.stderr) inside the worker processes. Although standard streams are documented to be safely isolated, standard error is left connected to a parent-owned operating system pipe.
Because the parent process does not active read or drain the standard error pipe during worker execution, any output routed to stderr persists in the pipe's internal kernel buffer. This lack of resource drainage introduces an immediate vulnerability where standard tracebacks, debug logging, or third-party print statements can fill the kernel buffer and stall execution.
The underlying vulnerability represents a failure to throttle or drain allocated operating system pipe buffers, classified as CWE-770. On modern kernel architectures, inter-process communication pipes employ finite ring buffers managed directly by the kernel. Under standard Linux configurations, this pipe buffer size defaults to 64 KiB (65,536 bytes), while Windows systems dynamically allocate similarly constrained resources.
When a child process outputs data to a standard pipe, the kernel buffers the payload until the parent process executes a read system call. If the child process writes a volume of data exceeding the buffer capacity, and the parent process is not actively draining the pipe, the operating system kernel blocks the writing thread. The writer process transitions into an uninterruptible sleep state waiting for buffer clearance.
In AnyIO versions prior to 4.14.2, the parent process awaits execution results from standard output (sys.stdout) while ignoring standard error (sys.stderr). If the worker process generates more than 64 KiB of diagnostic data on stderr, the write calls block. Consequently, the worker process stalls midway, and the parent process blocks indefinitely waiting for the worker to finish, creating a permanent deadlock.
The sequential flow of the deadlock condition highlights the blockages occurring on both sides of the inter-process pipe.
As the diagram demonstrates, the deadlock state cannot resolve itself because neither process can advance. The worker process cannot complete its execution loop to write a success token to the stdout pipe, and the parent process does not read from the stderr pipe that is blocking the worker.
The bug resides in process_worker() located within src/anyio/to_process.py. In affected versions of AnyIO, the worker initialization routine actively redirects standard input and standard output to null devices to avoid protocol pollution but leaves standard error unhandled.
# Vulnerable Implementation (Pre-4.14.2)
def process_worker() -> None:
stdout = sys.stdout
sys.stdin = open(os.devnull)
sys.stdout = open(os.devnull, "w")
# sys.stderr is completely omitted and remains connected to the parent pipe!
stdout.buffer.write(b"READY\n")
while True:
# Worker loop waiting for payloadTo remediate the vulnerability, the standard error stream must be redirected to os.devnull alongside the standard output stream. The fix merged in commit f1b7301c8264b0d2e8d24a5788fd29e93dea4040 implements this behavior:
# Patched Implementation (4.14.2)
def process_worker() -> None:
stdout = sys.stdout
sys.stdin = open(os.devnull)
sys.stdout = open(os.devnull, "w")
sys.stderr = open(os.devnull, "w") # Safe redirection of standard error
stdout.buffer.write(b"READY\n")
while True:
# Worker loop waiting for payloadWith this single-line modification, any diagnostic messages, warnings, or debug statements emitted during execution are discarded by the kernel, preventing pipe buffer saturation.
Exploiting this deadlock requires the ability to influence input parsed by the process pool worker, causing it to write verbose output to the standard error stream. This threat model is common in systems executing user-submitted scripts or parsing malicious inputs that trigger expansive logging or tracebacks.
An attacker can trigger the vulnerability by supplying input that forces deep recursive errors, validation warning logs, or direct diagnostic dumps. In configurations allowing untrusted code execution, a simple script writing directly to sys.stderr is sufficient to fill the 64 KiB buffer.
A typical proof-of-concept payload targeting vulnerable implementations is structured as follows:
import anyio
import sys
from anyio import to_process
def malicious_worker():
# Generate a payload larger than the 64 KiB OS pipe buffer
payload = "X" * (100 * 1024)
sys.stderr.write(payload)
sys.stderr.flush() # This call blocks indefinitely under AnyIO < 4.14.2
return "success"
async def main():
print("[*] Dispatching process pool worker...")
# The execution will freeze indefinitely here without timeout configuration
await to_process.run_sync(malicious_worker)
if __name__ == "__main__":
anyio.run(main)When executed on a vulnerable system, the parent process remains blocked indefinitely during the run_sync call.
The security impact of CVE-2026-64847 is focused exclusively on the availability of the target system, carrying a CVSS score of 6.8. Successful exploitation results in a persistent denial-of-service condition affecting the application pool.
Because AnyIO is a foundational library for high-throughput asynchronous applications, a single blocked worker process can rapidly consume available worker slots in the application process pool. Once the process pool is exhausted, any subsequent requests requiring subprocess isolation will hang, causing wider application degradation.
The attack vector is local (AV:L), meaning the attacker must be capable of executing code or triggering input-driven errors within the worker context. The vulnerability does not present potential for privilege escalation, confidentiality loss, or integrity compromise.
The recommended remediation path is upgrading the AnyIO installation to version 4.14.2 or newer, which completely closes the vulnerable code path by redirecting worker sys.stderr streams.
If upgrading is not immediately possible, developers can implement programmatic mitigation within the synchronous execution unit itself. Manually redirecting sys.stderr to a null target during task startup ensures safety:
import os
import sys
def safe_worker_context():
original_stderr = sys.stderr
sys.stderr = open(os.devnull, "w")
try:
# Execute standard application logic here
pass
finally:
sys.stderr.close()
sys.stderr = original_stderrAdditionally, defense-in-depth measures should include enforcing strict timeouts using anyio.fail_after() when waiting on process pool tasks to prevent infinite application hangs.
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
anyio agronholm | < 4.14.2 | 4.14.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-770 |
| Attack Vector | Local (AV:L) |
| CVSS Score | 6.8 (Medium) |
| EPSS Score | Not currently indexed |
| Impact | High Availability Impact (Denial of Service) |
| Exploit Status | PoC |
| KEV Status | Not listed in CISA KEV |
The software allocates a finite resource without imposing limits or implementing proper mechanisms to drain, restrict, or throttle consumption, leading to a permanent depletion of that resource (deadlock/hang).
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.
CVE-2026-81505 is a high-severity Broken Object Level Authorization (BOLA) / Insecure Direct Object Reference (IDOR) vulnerability in Convoy, a cloud-native webhooks gateway. In affected versions prior to 26.6.8, the single-item Source retrieval API endpoint authorizes project access but fails to confirm if the requested Source belongs to that specific project. This logical flaw allows authenticated users or project-scoped API key holders to bypass tenant isolation boundaries and retrieve unredacted, plaintext message broker credentials for Apache Kafka, Amazon SQS, RabbitMQ, and Google Cloud Pub/Sub belonging to other tenants. This issue is fully patched in version 26.6.8.
CVE-2026-77339 is a critical security vulnerability in Process Compose before version 1.120.0. The Model Context Protocol (MCP) Server-Sent Events (SSE) listener transport subsystem fails to validate the HTTP Host and Origin headers, and does not enforce authentication. This omissions expose local loopback listeners to DNS rebinding attacks orchestrated by malicious remote websites visited by developers, enabling unauthorized process control and arbitrary command execution.