From aee96932920e4948d7fccfb99e4fe3e6243141a9 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Sat, 15 Aug 2026 06:48:16 -0700 Subject: [PATCH] fix(file_functions): correct three integer conversions in the read path get_file_size() stored an LSEEK result in an int. LSEEK returns a 64-bit offset, so a successful seek back to a position at or beyond 2 GB truncated to a negative int and the function reported failure for a seek that worked. ccx_demuxer_get_file_size() is the same routine written correctly with an LLONG; this copy had drifted. Latent today -- the only caller opens each file fresh, so the position is always 0 -- but it is a trap for the next caller. buffered_read_opt() computed the bytes left in the file buffer as "bytesinbuffer - filebuffer_pos" on two unsigned ints. That is the underflow that made the bounds checks in file_buffer.h pass when they should have failed; the helper added to fix those, buffered_bytes_left(), belongs here too. The end-of-input test compared inputsize, a signed 64-bit size, against origin_buffer_size, a size_t. The usual arithmetic conversions turned a negative inputsize -- what get_filesize() returns when the size cannot be determined -- into a huge unsigned value, so the test silently read as "the input is enormous". The comparison is now spelled out. An unknown size still does not count as fitting in one read, and the short-circuit order is unchanged, so switch_to_next_file() is called exactly when it was before. Verified against the current master binary: byte-identical output on 99 files from the sample library, and on the --bufferinput, multi-file concat and stdin paths, with matching exit codes throughout. --- src/lib_ccx/file_functions.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib_ccx/file_functions.c b/src/lib_ccx/file_functions.c index 194d4b946..8a46bb6f1 100644 --- a/src/lib_ccx/file_functions.c +++ b/src/lib_ccx/file_functions.c @@ -11,7 +11,10 @@ int iResult = 0; LLONG get_file_size(int in) { - int ret = 0; + /* LSEEK returns a 64-bit offset. Storing it in an int truncated the result, so a + successful seek back to a position at or beyond 2 GB could land on a negative + value and be reported as a failure. */ + LLONG ret = 0; LLONG current = LSEEK(in, 0, SEEK_CUR); LLONG length = LSEEK(in, 0, SEEK_END); if (current < 0 || length < 0) @@ -344,7 +347,7 @@ size_t buffered_read_opt(struct ccx_demuxer *ctx, unsigned char *buffer, size_t // for the data to come up sleepandchecktimeout(seconds); } - size_t ready = ctx->bytesinbuffer - ctx->filebuffer_pos; + size_t ready = buffered_bytes_left(ctx); if (ready == 0) // We really need to read more { if (!ccx_options.buffer_input) @@ -417,7 +420,16 @@ size_t buffered_read_opt(struct ccx_demuxer *ctx, unsigned char *buffer, size_t { /* If live stream, don't try to switch - acknowledge eof here as it won't cause a loop end */ - if (ccx_options.live_stream || ((struct lib_ccx_ctx *)ctx->parent)->inputsize <= origin_buffer_size || !(ccx_options.binary_concat && switch_to_next_file(ctx->parent, copied))) + /* inputsize is a signed 64-bit size while origin_buffer_size is unsigned, + so the comparison used to convert inputsize to unsigned. A negative + inputsize -- what get_filesize() reports when the size could not be + determined -- turned into a huge value, and the test silently read as + "the input is enormous". Say what is meant instead. An unknown size + still does not count as fitting in one read, so behaviour is unchanged. */ + LLONG parent_inputsize = ((struct lib_ccx_ctx *)ctx->parent)->inputsize; + int input_fits_in_one_read = parent_inputsize >= 0 && + (uint64_t)parent_inputsize <= (uint64_t)origin_buffer_size; + if (ccx_options.live_stream || input_fits_in_one_read || !(ccx_options.binary_concat && switch_to_next_file(ctx->parent, copied))) eof = 1; } ctx->filebuffer_pos = keep;