返回资讯中心
外部精选
软件工程
#开发者实时流

Mozilla reported HTTP2Shell with proof of RCE in April

(6 files, 4 external files, 1 obsolete file)

Hacker News 最新charleyablaze2026/10/25 · 11 天前25 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

Attachments

(6 files, 4 external files, 1 obsolete file)

test.html

6 months ago Bugmon [:jkratzer for issues]

225 bytes, text/html

Details

fix.patch

6 months ago Bugmon [:jkratzer for issues]

2.40 KB, patch

Details | Diff | Splinter Review

server.py

6 months ago Bugmon [:jkratzer for issues]

5.77 KB, text/plain

Details

gen_stream.py

6 months ago Bugmon [:jkratzer for issues]

6.08 KB, text/plain

Details

gen_stream.cpython-312.pyc

6 months ago Bugmon [:jkratzer for issues]

6.71 KB, application/octet-stream

Details

README.md

6 months ago Bugmon [:jkratzer for issues]

2.13 KB, text/plain

Details

crash_stack.txt

6 months ago Bugmon [:jkratzer for issues]

3.07 KB, text/plain

Details

(secure)

6 months ago Ryan VanderMeulen [:RyanVM]

48 bytes, text/x-phabricator-request

tjr

:

sec-approval+

Details | Review

(secure)

6 months ago Ryan VanderMeulen [:RyanVM]

48 bytes, text/x-phabricator-request

phab-bot

:

approval-mozilla-beta+

Details | Review

(secure)

6 months ago Ryan VanderMeulen [:RyanVM]

48 bytes, text/x-phabricator-request

phab-bot

:

approval-mozilla-esr140+

Details | Review

(secure)

6 months ago Ryan VanderMeulen [:RyanVM]

48 bytes, text/x-phabricator-request

phab-bot

:

approval-mozilla-esr115+

Details | Review

Show External Show Obsolete

Bottom ↓

Tags ▾

Reset

Timeline ▾

Reset

Collapse All

Expand All

Comments Only

Bugmon [:jkratzer for issues]

Reporter

Description

• 6 months ago

The brotli decoder vendored in Firefox contains a heap buffer overflow that can be triggered by a malicious HTTP server serving a crafted ~22MB brotli-compressed response. The decoder initializes block_length[2] to BROTLI_BLOCK_SIZE_CAP (1<<24 = 16,777,216) under the assumption that no metablock can issue more distance codes than its declared output size. However, the dictionary transform path at decode.c:2247 permits zero-output transforms when distance_code > 120 — each such command decrements block_length[2] without consuming any of meta_block_remaining_len. After 16M such commands, block_length[2] reaches zero while the metablock is still active.

When block_length[2]==0 and num_block_types[2]==1 (the default), DecodeBlockTypeAndLength returns BROTLI_FALSE at decode.c:1189 because there is no block-type tree to read. The BROTLI_SAFE macro at decode.c:2139 misinterprets this BROTLI_FALSE as BROTLI_DECODER_NEEDS_MORE_INPUT and jumps to saveStateAndReturn. The streaming retry loop at decode.c:2444-2455 then assumes one more input byte will allow progress: it writes that byte to s->buffer.u8[s->buffer_length++] and retries. Since the bug is structural rather than input-shortage, the retry hits the same BROTLI_FALSE indefinitely, and buffer_length grows past 8 with no bound check (BROTLI_DCHECK is compiled out in release builds).

The BrotliDecoderState struct places buffer_length (uint64_t), pos (int), max_backward_distance (int), max_distance (int), ringbuffer_size, ringbuffer_mask, dist_rb_idx, dist_rb[4], error_code, meta_block_remaining_len, and the ringbuffer pointer immediately after the 8-byte buffer.u8[] array. The attacker controls every byte written, allowing them to set buffer_length carefully (e.g., u8[8]=0x08 to keep the loop sequential), then overwrite pos with an arbitrary 32-bit offset and max_distance/max_backward_distance with values that bypass bounds checks. Once the unsafe ProcessCommands path activates, memmove16(&s->ringbuffer[pos], ...) at decode.c:2283 writes 16 bytes at an attacker-chosen heap offset. With further bytes the attacker can also corrupt the ringbuffer pointer itself for arbitrary write-what-where. This is a remote, no-interaction memory corruption — visiting any webpage that fetches a brotli resource from an attacker-controlled origin (or a MitM on plain HTTP) is sufficient.

File: modules/brotli/dec/state.c, line 112-119

void BrotliDecoderStateMetablockBegin(BrotliDecoderState* s) { s->meta_block_remaining_len = 0; s->block_length[0] = BROTLI_BLOCK_SIZE_CAP; s->block_length[1] = BROTLI_BLOCK_SIZE_CAP; s->block_length[2] = BROTLI_BLOCK_SIZE_CAP; /* Assumes max 16M distance codes / s->num_block_types[0] = 1; s->num_block_types[1] = 1; s->num_block_types[2] = 1; / Single type → no block-switch tree */

File: modules/brotli/dec/state.h, line 264-280

/* Temporary storage for remaining input. Brotli stream format is designed in a way, that 64 bits are enough to make progress in decoding. / union { uint64_t u64; uint8_t u8[8]; } buffer; brotli_reg_t buffer_length; / uint64_t on 64-bit — first thing overwritten */

int pos; /* used as ringbuffer index */ int max_backward_distance; int max_distance; int ringbuffer_size; int ringbuffer_mask; int dist_rb_idx; int dist_rb[4]; int error_code; int meta_block_remaining_len;

uint8_t* ringbuffer; /* eventually overwritable too */

File: modules/brotli/dec/state.h, line 388-390

/* Literal/Command/Distance block size maximum; same as maximum metablock size; used as block size when there is no block switching. */ #define BROTLI_BLOCK_SIZE_CAP (1U << 24)

File: modules/brotli/dec/decode.c, line 2241-2253

} else { len = BrotliTransformDictionaryWord(&s->ringbuffer[pos], word, len, transforms, transform_idx); BROTLI_LOG(("[ProcessCommandsInternal] dictionary word: [%.*s]," " transform_idx = %d, transformed: [%.s]\n", i, word, transform_idx, len, &s->ringbuffer[pos])); if (len == 0 && s->distance_code <= 120) { / BUG: distance_code > 120 escapes / BROTLI_LOG(("Invalid length-0 dictionary word after transform\n")); return BROTLI_FAILURE(BROTLI_DECODER_ERROR_FORMAT_TRANSFORM); } } pos += len; / len==0 → no progress / s->meta_block_remaining_len -= len; / len==0 → not consumed */

File: modules/brotli/dec/decode.c, line 1854

--s->block_length[2]; /* Decremented unconditionally per distance code */

File: modules/brotli/dec/decode.c, line 1179-1191

static BROTLI_INLINE BROTLI_BOOL 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_SIZE_258]; const HuffmanCode* len_tree = &s->block_len_trees[ tree_type * BROTLI_HUFFMAN_MAX_SIZE_26]; 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) { return BROTLI_FALSE; /* BUG: returned to BROTLI_SAFE which treats it as NEEDS_MORE_INPUT */ }

File: modules/brotli/dec/decode.c, line 1943-1953

#define BROTLI_SAFE(METHOD)
{
if (safe) {
if (!Safe##METHOD) {
result = BROTLI_DECODER_NEEDS_MORE_INPUT; \ /* Misinterprets structural FALSE */ goto saveStateAndReturn;
}
} else {
METHOD;
}
}

File: modules/brotli/dec/decode.c, line 2138-2141

if (BROTLI_PREDICT_FALSE(s->block_length[2] == 0)) { BROTLI_SAFE(DecodeDistanceBlockSwitch(s)); /* → BROTLI_FALSE → NEEDS_MORE_INPUT */ } BROTLI_SAFE(ReadDistance(s, br));

File: modules/brotli/dec/decode.c, line 2444-2456

} else if (available_in != 0) { / Not enough data in buffer, but can take one more byte from input stream. / result = BROTLI_DECODER_SUCCESS; BROTLI_DCHECK(s->buffer_length < 8); / COMPILED OUT in release */ s->buffer.u8[s->buffer_length] = *next_in; / OOB WRITE: index 8, 9, 10... */ s->buffer_length++; BrotliBitReaderSetInput(br, &s->buffer.u8[0], s->buffer_length); (*next_in)++; (available_in)--; / Retry with more data in buffer. / continue; / Loops forever — bug never clears */ }

File: modules/brotli/common/platform.h, line 464-470

#if defined(BROTLI_DEBUG) || defined(BROTLI_ENABLE_LOG) #define BROTLI_ENABLE_DUMP_DEFAULT 1 #define BROTLI_DCHECK(x) assert(x) #else #define BROTLI_ENABLE_DUMP_DEFAULT 0 #define BROTLI_DCHECK(x) /* No-op in release — no protection */ #endif

File: modules/brotli/dec/decode.c, line 2270-2283

} else { int src_start = (pos - s->distance_code) & s->ringbuffer_mask; uint8_t* copy_dst = &s->ringbuffer[pos]; /* pos is ATTACKER-CONTROLLED via overflow / uint8_t copy_src = &s->ringbuffer[src_start]; int dst_end = pos + i; int src_end = src_start + i; /* Update the recent distances cache. / s->dist_rb[s->dist_rb_idx & 3] = s->distance_code; ++s->dist_rb_idx; s->meta_block_remaining_len -= i; / There are 32+ bytes of slack in the ring-buffer allocation. Also, we have 16 short codes, that make these 16 bytes irrelevant in the ring-buffer. Let's copy over them as a first guess. / memmove16(copy_dst, copy_src); / 16-byte heap write at attacker offset */

The vulnerability arises from an interaction between four pieces of code. (1) BrotliDecoderStateMetablockBegin in state.c assumes block_length[2]=16M is sufficient because a metablock cannot output more than 16M bytes — but it failed to account for commands that produce zero output. (2) The dictionary transform path explicitly permits len==0 when distance_code>120, creating the zero-output gadget. (3) DecodeBlockTypeAndLength returns BROTLI_FALSE when there is only one block type, which is semantically 'no block switch needed' but is indistinguishable from 'not enough input bits' to the BROTLI_SAFE macro. (4) The streaming retry loop trusts that NEEDS_MORE_INPUT means progress is possible with more bytes, and has only a debug-build assertion guarding the array bound. The BrotliDecoderState struct layout places critical state (buffer_length, pos, max_distance, ringbuffer pointer) immediately after the overflowed 8-byte buffer, turning a linear overflow into full decoder state corruption and ultimately a controlled heap write.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →