Skip to content

[Security] Inflator DEFLATE dynamic-block heap OOB write: HLIT/HDIST not range-checked, codeLengths[318] written (zinflate.cpp:457) #1368

Description

@afldl

Summary

Crypto++'s Inflator (DEFLATE decoder) has a heap out-of-bounds write in
zinflate.cpp:457. The code-length table is sized 318
(FixedSizeSecBlock<unsigned int, 286+32> at zinflate.cpp:406) but the fill
loop bound (zinflate.cpp:421) uses raw 5-bit HLIT/HDIST fields (0..31,
never range-checked; RFC 1951 valid max 29). With HLIT=31, HDIST=31 the
bound is 320 and std::fill(codeLengths + i, codeLengths + i + count, repeater) writes codeLengths[318] and [319] -- two words past the buffer.
malloc_usable_size confirms the 1272-byte allocation is exactly sized, so
the 8 bytes land in the next heap chunk's header region (heap-metadata
corruption -> crash/DoS; values written are attacker-controlled 0-15).

CWE-787 (heap OOB write). CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5
(High if the embedding service inflates untrusted data; Medium otherwise).

Affected version

Crypto++ 8.9.0 and current master (zinflate.cpp:406/421/457).

Root cause

zinflate.cpp, Inflator::DecodeHeader():

FixedSizeSecBlock<unsigned int, 286+32> codeLengths;   // 318 elements
...
unsigned int hlit  = m_reader.GetBits(5);              // 0..31, NOT range-checked
unsigned int hdist = m_reader.GetBits(5);              // 0..31, NOT range-checked
...
for (i=0; i < hlit+257+hdist+1; )                      // up to 320
{
    ...
    std::fill(codeLengths + i, codeLengths + i + count, repeater); // line 457
    i += count;
}

RFC 1951 valid max is HLIT <= 29 / HDIST <= 29 (286+30=316 entries); the
array is 318; the loop allows 320. The guard at line 455 only checks against
the attacker-chosen cap (i + count > hlit+257+hdist+1), never against the
buffer size 318. (zlib sizes this table lens[320] precisely because the
5-bit fields are unchecked.)

PoC (attachment)

See attached gist: https://gist.github.com/afldl/a7fc72e73fc80878333e803bcdb94562

File Purpose
probe.cpp Minimal driver: feeds the evil stream into CryptoPP::Inflator
repro.sh Instrumented build + run (prints each fill range)
evil.deflate 16-byte zlib stream (recreate via printf, see README)
output.txt Real run output: fill reaches [276,319) - index 318 is OOB
README.md Full reproduction + suggested fix

Reproduction:

git clone https://github.com/weidai11/cryptopp.git
cd cryptopp && make -j$(nproc)
printf '\xfd\xff\x81\x08\x00\x00\x00\x00\x20\xf8\xfb\x03\x01\x00\x00' > evil.deflate
# run probe.cpp (include path: cryptopp source)

Expected output (real, instrumented):

codeLengths = FixedSizeSecBlock<unsigned int, 286+32> = 318 elements [0..317]

[fill] i=0 count=138 -> [0,138)
[fill] i=138 count=138 -> [138,276)
[fill] i=276 count=43 -> [276,319)

The fill [276,319) writes codeLengths[318] -- past the 318-element buffer.
malloc_usable_size of the 1272-byte allocation returns exactly 1272, so
bytes 1272-1279 (index 318/319) land in the next heap chunk's header.
(ASAN does not flag this 8-byte overflow because glibc rounds the malloc up;
the source trace + malloc_usable_size establish the OOB independently.)

Suggested fix

Range-check HLIT/HDIST before the loop (reject > 29), or size the
array 320 like zlib does (lens[320]).

Credit

Reported by afldl, 2026-07.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions