seekable: reject seek tables exceeding ZSTD_SEEKABLE_MAXFRAMES#4685
Open
evilgensec wants to merge 1 commit into
Open
seekable: reject seek tables exceeding ZSTD_SEEKABLE_MAXFRAMES#4685evilgensec wants to merge 1 commit into
evilgensec wants to merge 1 commit into
Conversation
ZSTD_seekable_loadSeekTable() read Number_Of_Frames from the seek table footer and used it to size the entry array and the table-size arithmetic without bounding it by ZSTD_SEEKABLE_MAXFRAMES, the limit the writer enforces in ZSTD_seekable_logFrame(). A crafted seek table could set Number_Of_Frames close to 2^32. On platforms with a 32-bit size_t this overflows the malloc(sizeof(seekEntry_t) * (numFrames + 1)) size computation, under-allocating the array while the parse loop still writes numFrames entries, a heap out-of-bounds write. 64-bit platforms are unaffected: the oversized allocation does not overflow and fails cleanly. Reject the value before these computations, matching the limit applied on write.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds defensive validation when loading a seek table to prevent oversized numFrames values from causing unsafe allocations or arithmetic issues during decompression.
Changes:
- Introduces an early rejection when
numFramesexceedsZSTD_SEEKABLE_MAXFRAMES. - Refactors
tableSize/frameSizecomputation into separate variables after validation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+396
to
+405
| U32 tableSize; | ||
| U32 frameSize; | ||
|
|
||
| /* numFrames is bounded by ZSTD_SEEKABLE_MAXFRAMES on write; reject larger | ||
| * values to avoid overflowing the entry-array allocation below. */ | ||
| if (numFrames > ZSTD_SEEKABLE_MAXFRAMES) { | ||
| return ERROR(corruption_detected); | ||
| } | ||
| tableSize = sizePerEntry * numFrames; | ||
| frameSize = tableSize + ZSTD_seekTableFooterSize + ZSTD_SKIPPABLEHEADERSIZE; |
Author
|
Closing this. I'll route the change through the project's preferred reporting process instead. Apologies for the noise. |
Author
|
Reopening to keep the fix available for review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
ZSTD_seekable_loadSeekTable()readsNumber_Of_Framesfrom the seek table footer and uses it, unbounded, to size:tableSize = sizePerEntry * numFrames(U32), andmalloc(sizeof(seekEntry_t) * (numFrames + 1)).It never checks the value against
ZSTD_SEEKABLE_MAXFRAMES, the limit the writer enforces inZSTD_seekable_logFrame()(zstdseek_compress.c). The reader therefore accepts a frame count the writer would never emit.Impact
A crafted seek table can set
Number_Of_Framesclose to2^32.size_tis 32-bit,sizeof(seekEntry_t) * (numFrames + 1)overflows. The entry array is under-allocated, but the cumulative-position loop still writesnumFramesentries — a heap out-of-bounds write.size_t, so it does not overflow; the allocation simply becomes huge and fails cleanly, so there is no out-of-bounds write. TheU32tableSize/frameSizearithmetic still wraps on these platforms.The footer field is part of the trailer of an otherwise valid seekable archive, so a reader handed an attacker-influenced file reaches this path.
Fix
Reject
Number_Of_Frames > ZSTD_SEEKABLE_MAXFRAMESbefore the size computations, mirroring the bound applied on write. With the limit in place neithersizePerEntry * numFrames(≤12 * 0x8000000) norsizeof(seekEntry_t) * (numFrames + 1)can overflow a 32-bitsize_t, and an over-large value is rejected withcorruption_detected.Testing
contrib/seekable_formatcompiles clean under-Wall -Wextra -Werror.make -C contrib/seekable_format/tests testpasses (round-trip, the two no-hang cases, empty-string magic, multi-decompress) — valid archives are unaffected, since the bound (0x8000000) is far above any frame count a writer produces.