Skip to content

decompress: reject output overrunning split literals and oversized blocks - #4899

Open
terrelln wants to merge 1 commit into
meta:devfrom
terrelln:2026-11-05-split-lit-buffer-overrun
Open

terrelln wants to merge 1 commit into
meta:devfrom
terrelln:2026-11-05-split-lit-buffer-overrun

Conversation

@terrelln

@terrelln terrelln commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

When a block's literals are split between dst and litExtraBuffer, the first part of the literals is stored at the end of the block's area in dst. A block that decodes to more than that area could overwrite these literals before reading them, and decode to wrong output:

  • ZSTD_safecopyDstBeforeSrc() used ZSTD_wildcopy() when src was more than 16 bytes ahead of dst, but ZSTD_wildcopy() can write up to 31 bytes past its length, onto source bytes not yet copied. Only use it when src is at least WILDCOPY_OVERLENGTH bytes ahead.
  • ZSTD_execSequenceEndSplitLitBuffer() only detected op starting inside the literals being copied. Reject any sequence whose output runs past the end of the literals it consumes, which also covers matches overwriting the literals of the next sequences.
  • ZSTD_decompressFrame() didn't check that a block decodes to at most blockSizeMax, unlike ZSTD_decompressContinue(). One-shot decompression accepted such blocks, including raw and RLE blocks larger than blockSizeMax.

Valid blocks never decode to more than MIN(blockSizeMax, dstCapacity), so the output stays at least 64 KB - 32 bytes behind the literals in dst, and isn't affected. All writes were within dst, so this was not a memory safety issue. Frames with blocks larger than the window are now rejected by the one-shot API, as they already were by streaming.

Add tests with a crafted block for the block and frame APIs, and for ZSTD_d_maxBlockSize with raw and RLE blocks.

…ocks

When a block's literals are split between dst and litExtraBuffer, the
first part of the literals is stored at the end of the block's area in
dst. A block that decodes to more than that area could overwrite these
literals before reading them, and decode to wrong output:

- ZSTD_safecopyDstBeforeSrc() used ZSTD_wildcopy() when src was more
  than 16 bytes ahead of dst, but ZSTD_wildcopy() can write up to 31
  bytes past its length, onto source bytes not yet copied. Only use it
  when src is at least WILDCOPY_OVERLENGTH bytes ahead.
- ZSTD_execSequenceEndSplitLitBuffer() only detected op starting inside
  the literals being copied. Reject any sequence whose output runs past
  the end of the literals it consumes, which also covers matches
  overwriting the literals of the next sequences.
- ZSTD_decompressFrame() didn't check that a block decodes to at most
  blockSizeMax, unlike ZSTD_decompressContinue(). One-shot decompression
  accepted such blocks, including raw and RLE blocks larger than
  ZSTD_d_maxBlockSize.

Valid blocks never decode to more than MIN(blockSizeMax, dstCapacity),
so the output stays at least 64 KB - 32 bytes behind the literals in
dst, and isn't affected. All writes were within dst, so this was not a
memory safety issue. Frames with blocks larger than the window are now
rejected by the one-shot API, as they already were by streaming.

Add tests with a crafted block for the block and frame APIs, and for
ZSTD_d_maxBlockSize with raw and RLE blocks.
@meta-cla meta-cla Bot added the CLA Signed label Nov 5, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants