From f75a0400c657cfe1b112894ab438d12fb2db6ab7 Mon Sep 17 00:00:00 2001 From: Kanishk Sachan Date: Fri, 10 Jul 2026 19:06:13 +0530 Subject: [PATCH 1/2] nl: reject -w values > i32::MAX to avoid capacity-overflow panic GNU nl bounds --number-width/-w to [1, 2147483647] and exits with "Numerical result out of range" for larger values. Without this guard, `printf '\n' | nl -w 9223372036854775807` panics with "capacity overflow" because `" ".repeat(number_width + 1)` overflows isize. Fixes #13347 --- src/uu/nl/src/helper.rs | 7 +++++-- tests/by-util/test_nl.rs | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index 6bbec85f444..bf2e68c3030 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -63,8 +63,11 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> } match opts.get_one::(options::NUMBER_WIDTH) { None => {} - Some(num) if *num > 0 => settings.number_width = *num, - Some(_) => errs.push(translate!("nl-error-invalid-line-width", "value" => "0")), + Some(num) if *num > 0 && *num <= i32::MAX as usize => settings.number_width = *num, + Some(num) => errs.push(translate!( + "nl-error-invalid-line-width", + "value" => num.to_string() + )), } if let Some(num) = opts.get_one::(options::JOIN_BLANK_LINES) { settings.join_blank_lines = *num; diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index c08c6a7b243..57bb0849134 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -187,6 +187,28 @@ fn test_number_width_zero() { } } +#[test] +fn test_number_width_too_large() { + // Values > i32::MAX must be rejected to match GNU nl behaviour and avoid + // a capacity-overflow panic in " ".repeat(number_width + 1). + for arg in ["-w2147483648", "--number-width=2147483648"] { + new_ucmd!() + .arg(arg) + .pipe_in("") + .fails() + .stderr_contains("Invalid line number field width: '2147483648': Numerical result out of range"); + } +} + +#[test] +fn test_number_width_max_i32() { + // i32::MAX (2147483647) is the largest value GNU nl accepts; it must not panic. + new_ucmd!() + .args(&["-w", "2147483647"]) + .pipe_in("") + .succeeds(); +} + #[test] fn test_invalid_number_width() { for arg in ["-winvalid", "--number-width=invalid"] { From 91586f2937961352e4fbf2cbf34a5f597ef7b4ac Mon Sep 17 00:00:00 2001 From: Kanishk Sachan Date: Wed, 15 Jul 2026 00:07:58 +0530 Subject: [PATCH 2/2] nl: use clap range validator for -w/--number-width Per reviewer request, validate --number-width via clap's native range parser (1..=2147483647) instead of a post-parse guard. Changes: - nl.rs: switch value_parser to clap::value_parser!(u64).range(1..=(i32::MAX as u64)) - helper.rs: simplify to a plain if-let since the range is already enforced; remove the now-unused translate! import - test_nl.rs: update error-message assertions to match clap's range error format ("is not in 1..=2147483647") instead of the old translation string Fixes #13347 --- src/uu/nl/src/helper.rs | 10 ++-------- src/uu/nl/src/nl.rs | 2 +- tests/by-util/test_nl.rs | 4 ++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index bf2e68c3030..0408adac65a 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -7,7 +7,6 @@ use std::ffi::OsString; use crate::options; -use uucore::translate; // parse_options loads the options into the settings, returning an array of // error messages. @@ -61,13 +60,8 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> Some(Ok(style)) => settings.footer_numbering = style, Some(Err(message)) => errs.push(message), } - match opts.get_one::(options::NUMBER_WIDTH) { - None => {} - Some(num) if *num > 0 && *num <= i32::MAX as usize => settings.number_width = *num, - Some(num) => errs.push(translate!( - "nl-error-invalid-line-width", - "value" => num.to_string() - )), + if let Some(&num) = opts.get_one::(options::NUMBER_WIDTH) { + settings.number_width = num as usize; } if let Some(num) = opts.get_one::(options::JOIN_BLANK_LINES) { settings.join_blank_lines = *num; diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 37ec112451a..34a4009c24c 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -376,7 +376,7 @@ pub fn uu_app() -> Command { .long(options::NUMBER_WIDTH) .help(translate!("nl-help-number-width")) .value_name("NUMBER") - .value_parser(clap::value_parser!(usize)), + .value_parser(clap::value_parser!(u64).range(1..=(i32::MAX as u64))), ) } diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 57bb0849134..b5aff70629d 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -183,7 +183,7 @@ fn test_number_width_zero() { new_ucmd!() .arg(arg) .fails() - .stderr_contains("Invalid line number field width: ‘0’: Numerical result out of range"); + .stderr_contains("is not in 1..=2147483647"); } } @@ -196,7 +196,7 @@ fn test_number_width_too_large() { .arg(arg) .pipe_in("") .fails() - .stderr_contains("Invalid line number field width: '2147483648': Numerical result out of range"); + .stderr_contains("is not in 1..=2147483647"); } }