From 484f47a60db9d6cd7de75c62c7af698066b66ea6 Mon Sep 17 00:00:00 2001 From: arbelonson-source <269032023+arbelonson-source@users.noreply.github.com> Date: Mon, 31 Aug 2026 01:55:11 +0300 Subject: [PATCH 1/2] split: keep a suffix start at the top of the range from overflowing `split` works out how wide the suffixes need to be from the start value plus the number of chunks. Either of those on its own can be as large as the type allows, so their sum is not always a value the type can hold: $ split -n 5 --numeric-suffixes=18446744073709551615 /dev/null thread 'main' panicked at src/uu/split/src/filenames.rs:202:36: attempt to add with overflow $ split -n 5 --hex-suffixes=ffffffffffffffff /dev/null thread 'main' panicked at src/uu/split/src/filenames.rs:202:36: attempt to add with overflow With overflow checks off the add wraps instead, and the width computed from the wrapped sum is too small. Add in a type with room for the sum. The width then comes out right, and the value is refused the way any other too-wide start is: $ split -n 5 --numeric-suffixes=18446744073709551615 /dev/null split: the suffix length needs to be at least 20 Fixes #13749. --- src/uu/split/src/filenames.rs | 8 +++++--- tests/by-util/test_split.rs | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index bc7adfb58d2..7399b66d925 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -199,9 +199,11 @@ impl Suffix { // Auto pre-calculate new suffix length (auto-width) if necessary if let Strategy::Number(number_type) = strategy { let chunks = number_type.num_chunks(); - let required_length = ((start as u64 + chunks) as f64) - .log(stype.radix() as f64) - .ceil() as usize; + // The last suffix is the start plus the chunk count, and either of + // those alone can be as large as the type allows, so the sum needs + // room the type does not have. + let last = start as u128 + u128::from(chunks); + let required_length = (last as f64).log(stype.radix() as f64).ceil() as usize; if (start as u64) < chunks && !(is_length_cmd_opt && length > 0) { // with auto-width ON the auto-widening is OFF diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 345ce271451..e88405d5f66 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -942,6 +942,42 @@ fn test_suffix_length_req() { .stderr_only("split: the suffix length needs to be at least 2\n"); } +/// The width the suffixes need is worked out from the start value plus the +/// number of chunks, and either of those on its own can be the largest value +/// there is, so the sum of them does not fit where they do. +#[test] +fn test_suffix_start_at_the_top_of_the_range() { + new_ucmd!() + .args(&["-n", "5", "--numeric-suffixes=18446744073709551615", "-"]) + .pipe_in("") + .fails_with_code(1) + .stderr_only("split: the suffix length needs to be at least 20\n"); + + // The hexadecimal start is read in base 16, so it takes fewer digits to + // reach the same place. + new_ucmd!() + .args(&["-n", "5", "--hex-suffixes=ffffffffffffffff", "-"]) + .pipe_in("") + .fails_with_code(1) + .stderr_only("split: the suffix length needs to be at least 16\n"); + + // A suffix wide enough for the start is accepted, and the first name is + // the start itself. + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "-n", + "5", + "--numeric-suffixes=18446744073709551615", + "-a", + "20", + "-", + ]) + .pipe_in("") + .fails_with_code(1) + .stderr_only("split: output file suffixes exhausted\n"); + assert!(at.file_exists("x18446744073709551615")); +} + #[test] fn test_large_suffix_length_is_rejected() { new_ucmd!() From 51689f07caccdd09efaa400b65cf37a4f32e7f0b Mon Sep 17 00:00:00 2001 From: arbelonson-source <269032023+arbelonson-source@users.noreply.github.com> Date: Mon, 31 Aug 2026 04:21:51 +0300 Subject: [PATCH 2/2] test(split): restrict the u64::MAX suffix-start test to 64-bit targets CI failed this test on i686 (and i686 windows): the start value `18446744073709551615` is `u64::MAX`, which does not fit in a 32-bit `usize`, so `--numeric-suffixes` rejects it at the parse step there with a different message than the overflow-guard one this test checks. The overflow this test exists for can only occur once `start` itself reaches `u64::MAX`, which requires a 64-bit `usize` to hold in the first place -- so on a 32-bit target there is nothing here left to overflow, and the test doesn't apply. --- tests/by-util/test_split.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index e88405d5f66..39e4baf6676 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -945,7 +945,13 @@ fn test_suffix_length_req() { /// The width the suffixes need is worked out from the start value plus the /// number of chunks, and either of those on its own can be the largest value /// there is, so the sum of them does not fit where they do. +/// +/// `u64::MAX` only reaches `--numeric-suffixes`'s `start` at all on a target +/// where `usize` is 64 bits -- on a 32-bit target the value is already +/// rejected by the `usize` parse, before the overflow this guards against +/// could occur. #[test] +#[cfg(target_pointer_width = "64")] fn test_suffix_start_at_the_top_of_the_range() { new_ucmd!() .args(&["-n", "5", "--numeric-suffixes=18446744073709551615", "-"])