Sep 19, 2026·6 min read·61 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).
CVE-2026-62374 (GHSA-f9j6-8p6x-r9j6) is a verification bypass vulnerability in the OpenWallet Foundation SD JWT JS library (@sd-jwt/core and @sd-jwt/decode versions 0.19.0 and earlier). Due to permissive UTF-8 decoding and the omission of mandatory reference checks during presentation verification, tampered or unreferenced disclosures are silently dropped rather than rejected. This deviates from RFC 9901 §7.1 requirements and allows attackers to execute silent claim-set reduction attacks against relying parties.
An interpretation conflict in Bytebase DBHub (< 0.22.6) allows attackers to bypass read-only SQL query restrictions when connected to MySQL or MariaDB instances. Because DBHub's custom SQL tokenizer treats any '--' sequence as a comment, whereas MySQL and MariaDB require trailing whitespace or a control character, crafted queries containing '--' immediately followed by non-whitespace characters allow stacked destructive SQL statements to execute on connections configured as read-only.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.