[FIX] Out-of-bounds read: unsigned underflow defeats the file buffer bounds checks - #2322
Open
cfsmp3 wants to merge 2 commits into
Open
[FIX] Out-of-bounds read: unsigned underflow defeats the file buffer bounds checks#2322cfsmp3 wants to merge 2 commits into
cfsmp3 wants to merge 2 commits into
Conversation
…passes
buffered_seek() handled a negative offset with
ctx->filebuffer_pos += offset;
if (ctx->filebuffer_pos < 0)
but filebuffer_pos is unsigned int, so the addition wraps instead of going
negative and the check can never be true. The guard below it compares
filebuffer_pos + startbytes_pos against 0 and is dead for the same reason, so
the "seek before buffer start" fatal() is unreachable.
Seeking back further than the current position therefore left filebuffer_pos at
roughly 2^32. buffered_read() then evaluated
bytes <= ctx->bytesinbuffer - ctx->filebuffer_pos
which underflows too, so the bounds check passed and memcpy() read from
filebuffer + 2.3e9 against a 1 MB buffer.
Observed on a malformed WTV file, where a corrupt chunk offset produces
buffered_seek(ctx, -1969339576) with filebuffer_pos = 1048576:
filebuffer_pos -> 2326676296
bytesinbuffer = 1048576
bytesinbuffer - filebuffer_pos = 1969339576 (underflow)
memcpy(dst, filebuffer + 2326676296, 32) -> SIGSEGV
Do the seek arithmetic in int64_t so both guards work as written, and route the
three buffer-space checks through a buffered_bytes_left() helper that returns 0
when the position is past the end. buffered_read_byte() had the same hazard and
would have indexed filebuffer[] out of bounds.
Input files are untrusted, so this is a bounds-check fix, not only a crash fix.
MSVC warns C4267 on "filebuffer_pos += bytes": filebuffer_pos is an unsigned int and bytes is a size_t. The warning predates this branch -- it fires 54 times on master -- but the guard this commit series added is what makes the conversion provably safe, so state that in the code instead of leaving a warning that a reader has to re-derive. bytes is known to be no larger than what is left of the buffer, and that remainder is itself an unsigned int, so the value always fits.
Collaborator
CCExtractor CI platform finished running the test files on linux. Below is a summary of the test results, when compared to test for commit b67effd...:
NOTE: The following tests have been failing on the master branch as well as the PR:
This PR does not introduce any new test failures. However, some tests are failing on both master and this PR (see above). Check the result page for more info. |
Collaborator
CCExtractor CI platform finished running the test files on windows. Below is a summary of the test results, when compared to test for commit b67effd...:
NOTE: The following tests have been failing on the master branch as well as the PR:
This PR does not introduce any new test failures. However, some tests are failing on both master and this PR (see above). Check the result page for more info. |
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.
A WTV recording makes ccextractor read ~2.3 GB past a 1 MB buffer and segfault. The buffer bounds check that should have stopped it is defeated by unsigned arithmetic.
Root cause
buffered_seek()handles a negative offset like this:filebuffer_posandstartbytes_posare bothunsigned int, so the addition wraps instead of going negative and neither guard can ever be true. The "seek before buffer start" path is dead code.A backward seek past the start of the buffer therefore leaves
filebuffer_posat roughly 2^32.buffered_read()then evaluateswhich underflows for the same reason, so the bounds check passes and
memcpy()reads from far outside the buffer.Observed
On
5f6dfe831e35…wtvthe WTV parser reachesbuffered_seek(ctx, -1969339576)whilefilebuffer_posis 1048576. Values captured at the fault:Fix
Do the seek arithmetic in
int64_tso both existing guards work as written, and route the buffer-space checks through a smallbuffered_bytes_left()helper that returns 0 when the position is past the end.Three call sites shared the hazard, so all three are converted:
buffered_read()— the crash abovebuffered_skip()— same comparisonbuffered_read_byte()—if (ctx->bytesinbuffer - ctx->filebuffer_pos)would be truthy on underflow and indexfilebuffer[]out of boundsInput files are untrusted, so this is a bounds-check fix rather than only a crash fix.
Verification
PANIC: Attempt to seek before buffer start) instead of faulting.What the remaining PANIC means
With this fix the sample no longer reads out of bounds; the guard that was previously dead now fires:
That message is accurate and should stay: reaching it means an invariant really was broken, and the invariant was broken by our own code rather than by the input.
Tracing the caller shows why.
get_data()reads a 32-bit element length straight from the file and doeslen -= 32with no check thatlen >= 32, then passeslen + padtoskip_sized_buffer(uint32_t size), which callsbuffered_seek(ctx, size)— and that parameter is anint. The observed size was 2325627720, which is aboveINT_MAX, so the conversion produced -1969339576 (2325627720 - 2^32). A forward skip silently became a 1.8 GB backward seek.For the avoidance of doubt, the input is not meaningfully damaged:
ffprobereports a well-formed WTV with four streams and a 358 s duration matching the file size, andffmpegdecodes the whole file to completion with exit 0 (a couple of localized glitches typical of a broadcast capture, nothing structural). The file is 0.34 GiB, so a 1.8 GiB backward seek could never have been legitimate.Fixing that properly means validating the parsed length and not narrowing a
uint32_tinto anintseek. That is a WTV-parser change and belongs in its own PR; this one stops the out-of-bounds read for every caller of the buffer layer.Related
Found while investigating a different segfault; that one is fixed separately in #2319. This branch does not fix it, and #2319 does not fix this one — they are independent bugs in different subsystems.