Skip to content

Fix stack-buffer-overflow in FASTCOVER with small samples (#4499)#4600

Open
Nicolas0315 wants to merge 2 commits intofacebook:devfrom
Nicolas0315:fix/fastcover-stack-buffer-overflow-small-samples
Open

Fix stack-buffer-overflow in FASTCOVER with small samples (#4499)#4600
Nicolas0315 wants to merge 2 commits intofacebook:devfrom
Nicolas0315:fix/fastcover-stack-buffer-overflow-small-samples

Conversation

@Nicolas0315
Copy link

Summary

Fix a stack-buffer-overflow (out-of-bounds read) in FASTCOVER_hashPtrToIndex triggered when training a dictionary with many small samples (e.g. 8 × 1-byte).

Root Cause

FASTCOVER_hashPtrToIndex reads d bytes (6 or 8) from the sample buffer via ZSTD_hash6Ptr/ZSTD_hash8Ptr. However:

  1. FASTCOVER_selectSegment iterated activeSegment.end up to end without ensuring that at least d bytes remain at the read position, causing OOB reads near the buffer boundary.
  2. FASTCOVER_buildDictionary computed epochEnd = epochBegin + epochs.size without clamping to the actual sample buffer size, so end could exceed the training data boundary.

Note: FASTCOVER_computeFrequency already has the correct bound check (start + readLength <= currSampleEnd), so this was a consistency issue only in the segment selection path.

Fix

  1. In FASTCOVER_selectSegment: add hashEnd = end - d + 1 and use it as the loop bound for all three hash-reading loops (main sliding window, cleanup, and frequency zeroing).
  2. In FASTCOVER_buildDictionary: clamp epochEnd to ctx->offsets[ctx->nbTrainSamples] (the actual end of training data).

Testing

Tested with ASAN + UBSAN using the PoC from #4499:

const char data[8] = {'A','B','C','D','E','F','G','H'};
const size_t sizes[8] = {1,1,1,1,1,1,1,1};
ZDICT_trainFromBuffer(dict, 1024, data, sizes, 8);

Before: AddressSanitizer: stack-buffer-overflow in FASTCOVER_hashPtrToIndex
After: No crash, returns a 135-byte dictionary.

Fixes #4499

FASTCOVER_hashPtrToIndex reads d bytes (6 or 8) from the sample
buffer, but FASTCOVER_selectSegment iterated up to 'end' without
ensuring d bytes remain, causing an out-of-bounds read when samples
are very small (e.g. 8 x 1-byte).

Two fixes:
1. In FASTCOVER_selectSegment: limit all hash-reading loops to
   'hashEnd = end - d + 1' instead of 'end', preventing reads
   past the buffer.
2. In FASTCOVER_buildDictionary: clamp epochEnd to the actual
   training data boundary (ctx->offsets[ctx->nbTrainSamples])
   so selectSegment never receives an end position beyond the
   sample buffer.

Tested with AddressSanitizer + UBSan using the PoC from facebook#4499
(8 x 1-byte samples). Before: stack-buffer-overflow. After: no
crash, returns a 135-byte dictionary.

Fixes facebook#4499
@meta-cla
Copy link

meta-cla bot commented Mar 1, 2026

Hi @Nicolas0315!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@terrelln
Copy link
Contributor

terrelln commented Mar 2, 2026

@Nicolas0315 can you please add that unit test to tests/fuzzer.c in basicUnitTests(). See 81cf153 for an example of doing that.

Copy link
Contributor

@sebres sebres left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall not the lower limit of sample be 8 bytes?
Related to the docu for ZDICT_trainFromBuffer:

zstd/lib/zdict.h

Lines 199 to 200 in c84c8fb

* Note: Dictionary training will fail if there are not enough samples to construct a
* dictionary, or if most of the samples are too small (< 8 bytes being the lower limit).

So it doesn't really make sense to train on such short samples.
Probably it shall simply check the size of sample and ZDICT_trainFromBuffer could simply bypass them (and then fail if everything is too short, exactly how the description says).

Add a unit test in basicUnitTests() that calls ZDICT_trainFromBuffer_fastCover
with 8 samples of 1 byte each (d=8, k=16).  This is the minimal reproducer from
issue facebook#4499; it triggered a stack-buffer-overflow in FASTCOVER_hashPtrToIndex
before the fix in this PR.  The test verifies the function does not crash
regardless of whether it returns a valid dictionary or an error.
@meta-cla meta-cla bot added the CLA Signed label Mar 15, 2026
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.

Stack buffer overflow in FASTCOVER_hashPtrToIndex when training with many 1-byte samples

3 participants