Sep 18, 2026·8 min read·13 visits
A logical flaw in ReactPHP's chunked parser allows remote attackers to block the single-threaded event loop indefinitely using malformed chunked transfer encoding payloads, leading to a complete Denial of Service.
An infinite loop vulnerability in ReactPHP's react/http chunked transfer encoding decoder (v0.6.0 up to 1.11.1) allows unauthenticated remote attackers to trigger a denial of service (DoS) by sending crafted chunked requests or responses, completely freezing the single-threaded event loop and pegging CPU usage to 100%.
ReactPHP relies on an event-driven, non-blocking I/O model implemented on a single-threaded execution model. The react/http component provides streaming HTTP server and client capabilities, allowing concurrent connection management within this single-threaded event loop. Under this architectural paradigm, any blocking operation or infinite loop within a stream decoder immediately halts the entire event loop, stopping execution for all concurrent requests and connections managed by that process.
The vulnerability identified as CVE-2026-84997 is a high-severity denial-of-service vulnerability situated within the stream processing engine of the HTTP message body decoder. Specifically, the flaw resides in the React\Http\Io\ChunkedDecoder class, which handles HTTP messages using Transfer-Encoding: chunked. This decoder is used symmetrically: it processes incoming request bodies for React\Http\HttpServer and incoming response bodies for React\Http\Browser (the ReactPHP HTTP client).
Because the component is deployed on the network perimeter to parse incoming HTTP data streams, it constitutes an exposed attack surface. By presenting specifically structured, malformed HTTP payloads, a remote unauthenticated attacker can exploit logical flaws in the state machine of the chunked decoder. This causes the parser to cycle infinitely without reducing its internal buffer, locking the entire process and generating sustained 100% CPU utilization across the affected core.
The technical root cause of CVE-2026-84997 lies in the event-driven data parser implemented within React\Http\Io\ChunkedDecoder::handleData($data). The class processes stream data incrementally by appending newly arrived data to an internal string buffer ($this->buffer) and executing a parsing state machine wrapped inside a while ($this->buffer !== '') loop. The parser logic relies on the invariant that every loop iteration must consume a positive number of bytes from the buffer, thereby reducing its length and ensuring the loop eventually terminates when the buffer is empty.
The vulnerability occurs under two distinct input-handling scenarios where this invariant is violated, causing the loop to run with an unchanging buffer state. The first scenario involves an incomplete terminal-chunk trailer. When a chunked stream delivers a terminal chunk (a chunk of size zero, represented by '0\r\n'), the parser searches for the terminating CRLF sequence ('\r\n') using PHP's native strpos function. If the trailer data is truncated or incomplete (such as '0\r\nab'), strpos fails to find the separator and returns the boolean value false.
The class subsequently attempts to slice the buffer up to the position of the CRLF using substr($this->buffer, $positionCrlf). In PHP, when the boolean value false is passed as the second argument to substr, weak type coercion converts it to the integer 0. Consequently, the operation evaluates to substr($this->buffer, 0), which returns the entire, unmodified buffer. Because the buffer size does not shrink, the while loop condition remains satisfied, and the engine enters an infinite loop, blocking the ReactPHP event loop.
The second scenario involves an off-by-one logical error in the parser's post-chunk validation guards. Under RFC 9112, each data chunk must be followed immediately by a CRLF. The decoder maintains an error guard to validate this requirement, which historically executed only if the remaining buffer length was strictly greater than 2 (strlen($this->buffer) > 2). If an attacker delivers a completed chunk followed by exactly two non-CRLF bytes (e.g., '2\r\nabXY'), both the wait guard and the error guard are bypassed. The parser fails to consume the trailing invalid bytes, leaving the buffer unchanged and initiating an infinite loop.
To comprehend the precise mechanics of the vulnerability and its remediation, we examine the implementation of handleData within src/Io/ChunkedDecoder.php. The vulnerable codebase relied on implicit type coercion and an incorrect length comparison operator to determine whether the stream has reached a valid termination state or contains protocol errors. Below is a comparative representation of the vulnerable parsing logic versus the patched implementation.
// Vulnerable Code Path in src/Io/ChunkedDecoder.php
while ($this->buffer !== '') {
// ... parsing chunk boundaries ...
if ($this->chunkSize === 0) {
// If no CRLF is found, strpos returns false
$positionCrlf = \strpos($this->buffer, "\r\n");
// BUG: false is coerced to 0, leaving buffer unchanged
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
}
// BUG: Strictly greater-than comparison bypasses exactly 2 bytes
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
$this->handleError(new Exception('Chunk does not end with a CRLF'));
return;
}
}The patch introduced in commit b6d4688790adf3797071fcf88a3fc4225f30486a addresses both flaws directly by introducing strict type validation and corrected mathematical bounds. First, the parser now explicitly checks if $positionCrlf evaluates to false and exits early, waiting for additional stream data. Second, the error guard comparison has been modified from strictly greater than (> 2) to greater than or equal to (>= 2). This forces immediate error handling and stream termination when the buffer contains invalid trailing data of exactly two bytes.
// Patched Code Path in src/Io/ChunkedDecoder.php
while ($this->buffer !== '') {
// ... parsing chunk boundaries ...
if ($this->chunkSize === 0) {
$positionCrlf = \strpos($this->buffer, "\r\n");
// FIX: Explicitly handle incomplete trailer state
if ($positionCrlf === false) {
return;
}
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
}
// FIX: Using >= 2 ensures exactly two non-CRLF bytes trigger error handling
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) >= 2) {
$this->handleError(new Exception('Chunk does not end with a CRLF'));
return;
}
}Exploitation of CVE-2026-84997 does not require privileges or user interaction, and can be executed over standard network channels. The attacker's objective is to transmit a malformed chunked transfer payload that forces the parser into the infinite loop execution path. Because ReactPHP relies on a single event-driven loop thread, a successful exploit instantly blocks all other operations on that thread, resulting in a denial-of-service condition for all concurrent users.
To exploit a server running React\Http\HttpServer, the attacker sends a standard HTTP POST request specifying Transfer-Encoding: chunked. Following the headers, the attacker transmits one of the two trigger payloads. For Scenario A, the payload consists of the terminal chunk marker followed by a non-CRLF character sequence without any closing CRLF (e.g., '0\r\nab'). For Scenario B, the attacker transmits a valid chunk of data, followed by exactly two non-CRLF characters (e.g., '2\r\nabXY').
The exploit can also target client-side applications utilizing React\Http\Browser to communicate with external resources. In this attack vector, a malicious or compromised web server responds to the client's request with a chunked-encoded response body containing the identical payload structures. Upon receipt and parsing of the response by the client's decoder, the client-side event loop freezes, halting all asynchronous tasks running on that process.
Evaluating the fix completeness of the initial patch reveals that while the logical bugs in the infinite loop paths were resolved, they uncovered a secondary vulnerability. In the initial fix (b6d4688790adf3797071fcf88a3fc4225f30486a), when an incomplete trailer was detected, the parser safely executed a return statement to await additional data. However, if an attacker continuously streamed trailer data without ever sending a terminating CRLF, the buffer would grow unboundedly, leading to system memory exhaustion.
To address this secondary exhaustion vector, the maintainers implemented a follow-up commit (58bc906dcaa5d360830555360bfdeacff3be0260). This patch introduces a hard limit on the allowable trailer size, utilizing a maximum buffer threshold (MAX_CHUNK_HEADER_SIZE of 1024 bytes). If the internal buffer length exceeds this threshold while waiting for the terminating CRLF, the parser throws an exception and closes the connection. This secondary patch is critical to achieving a complete and robust remediation.
With both the infinite loop and the unbounded buffering issues resolved, the current implementation in version 1.11.1 provides complete protection against these attack paths. No obvious bypasses of the state machine constraints remain, as both logical truncation and resource exhaustion vectors have been bounded. Organizations must ensure that the secondary patch is deployed alongside the primary logic changes to prevent alternative Denial of Service vectors.
Remediation of CVE-2026-84997 requires updating the react/http package to version 1.11.1 or higher. This update can be performed using Composer by modifying the project's dependency constraints and executing an update command. Developers must verify that their lock files reflect the installation of the corrected version to ensure that production environments are not running vulnerable code paths.
composer require react/http:^1.11.1For environments where immediate package updates are impossible, operational mitigations can protect the server-side attack surface. Deploying a robust reverse proxy such as Nginx or HAProxy in front of the ReactPHP application server effectively filters out malformed HTTP requests. These proxies normalize the incoming HTTP stream, reassembling and validating chunked transfer encoding before passing a cleaned request to the ReactPHP backend.
Note that reverse proxy normalization only mitigates the inbound (server) direction of the vulnerability. It provides no defense for outbound clients implemented via React\Http\Browser that connect to malicious third-party endpoints. For outbound client scenarios, the only complete mitigation is upgrading the library to the patched version.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
react/http reactphp | >= 0.6.0, < 1.11.1 | 1.11.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-835 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 7.5 (High) |
| Exploit Status | PoC available in test suite |
| KEV Status | Not Listed |
| Impact | Denial of Service (CPU exhaustion and event-loop freeze) |
The program contains an iteration loop with an exit condition that cannot be reached or is bypassed under specific malformed input conditions, causing execution to spin indefinitely.
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.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.