Fix heap OOB read in switch_to_next_file(): sync inputfile array with num_input_files (#2218)

The Rust FFI function copy_from_rust() computed num_input_files by filtering
empty strings from the inputfiles Vec, but passed an unfiltered clone to
string_to_c_chars() to build the C inputfile[] array. This mismatch made the
C array length and num_input_files disagree: switch_to_next_file() could index
inputfile[current_file] where current_file < num_input_files but >= array size,
reading one slot past the end of the allocated array — confirmed by
AddressSanitizer (heap-buffer-overflow at file_functions.c:183).

The same count/size mismatch also caused free_rust_c_string_array() to
reconstruct the Vec with an incorrect capacity, producing heap corruption on
every clean shutdown.

Fix: filter empty strings into a single Vec<String> first, then derive both
num_input_files (filtered.len()) and the C array (string_to_c_chars(filtered))
from that same source, eliminating the mismatch entirely.

Fixes #2182

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Abhijeet Kumar
2026-03-29 01:02:41 +05:30
committed by GitHub
parent c4573bdced
commit 1b4123b302

View File

@@ -257,9 +257,18 @@ pub unsafe fn copy_from_rust(ccx_s_options: *mut ccx_s_options, options: Options
// Freeing them would cause use-after-free and double-free errors.
if let Some(ref inputfile) = options.inputfile {
if (*ccx_s_options).inputfile.is_null() {
(*ccx_s_options).inputfile = string_to_c_chars(inputfile.clone());
(*ccx_s_options).num_input_files =
inputfile.iter().filter(|s| !s.is_empty()).count() as _;
// Filter empty strings into a single Vec so that the C array length
// and num_input_files are always derived from the same source.
// Previously string_to_c_chars received the full (unfiltered) Vec
// while num_input_files was the filtered count, causing a mismatch
// that let switch_to_next_file() read past the end of the array.
let filtered: Vec<String> = inputfile
.iter()
.filter(|s| !s.is_empty())
.cloned()
.collect();
(*ccx_s_options).num_input_files = filtered.len() as _;
(*ccx_s_options).inputfile = string_to_c_chars(filtered);
}
}
(*ccx_s_options).demux_cfg = options.demux_cfg.to_ctype();