Skip to content

dibio: fix unsigned underflow in DiB_findMaxMem, guard nbSamples overflow#4708

Open
tojixlugar wants to merge 1 commit into
facebook:devfrom
tojixlugar:patch-1
Open

dibio: fix unsigned underflow in DiB_findMaxMem, guard nbSamples overflow#4708
tojixlugar wants to merge 1 commit into
facebook:devfrom
tojixlugar:patch-1

Conversation

@tojixlugar

Copy link
Copy Markdown

What

Two small robustness fixes in programs/dibio.c:

  1. DiB_findMaxMem() unsigned underflow > the memory-probing retry loop
    had no lower bound on requiredMem. If every allocation attempt down to
    step (8MB) failed, the next decrement would wrap requiredMem (an
    unsigned long long) around to a value near ULLONG_MAX instead of
    exiting, turning a legitimate OOM condition into an unbounded loop.

  2. sampleSizes allocation guard > fs.nbSamples is accumulated as a
    plain int in DiB_fileStats() and could theoretically overflow with a
    large/heavily-chunked input set. Added an explicit check before it's used
    to size the sampleSizes malloc, rather than relying on the allocation
    failing and being caught by a later unrelated check.

Why

Neither is reachable in normal use, but both leave the failure path
undefined/fragile rather than cleanly reported, which matters for a tool
that's meant to fail loudly (see EXM_THROW usage throughout this file)
rather than hang or behave unpredictably.

Testing

Built with make -C programs zstd and ran zstd --train against a sample
corpus to confirm normal dictionary training output is unchanged. Both
fixes only change behavior on the previously-unreachable failure paths.

Scope

Minimal, localized diff no behavior change on the success path, no API
changes.

…flow

DiB_findMaxMem()'s retry loop decremented requiredMem (unsigned long long)
with no floor check. If malloc() kept failing all the way down to `step`
(8MB), the next `requiredMem -= step` would underflow and wrap around to
a value near ULLONG_MAX instead of terminating, turning what should be a
clean out-of-memory failure into an unbounded loop of futile allocation
attempts.

Fix: stop the loop once requiredMem would drop to or below `step`, and
fail explicitly via EXM_THROW if no allocation ever succeeds, instead of
silently returning an unusable value.

Also added an explicit check that fs.nbSamples is positive before it's
used to size the sampleSizes allocation in DiB_trainFromFiles(). nbSamples
is accumulated as a plain int in DiB_fileStats() and could in principle
overflow with a large enough/chunked input set; previously this relied
on malloc() happening to fail and being caught by a later check further
down. Failing fast at the point of computation is clearer and doesn't
depend on that side effect.

Both changes are no-ops on the normal/success path verified by running
`zstd --train` against a sample corpus and confirming dictionary output
is unchanged.
@meta-cla meta-cla Bot added the CLA Signed label Jul 9, 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.

1 participant