From 5f74be7499c7f266059c98063bf61dbb9313287e Mon Sep 17 00:00:00 2001 From: Arihan Yadav Date: Mon, 10 Aug 2026 15:24:09 -0700 Subject: [PATCH 1/2] printf: avoid panic on large numeric field widths --- .../src/lib/features/format/num_format.rs | 36 +++++++++++++------ tests/by-util/test_printf.rs | 12 +++++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index b52859de908..9314f35fd0d 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -749,6 +749,17 @@ fn strip_fractional_zeroes_and_dot(s: &mut String) { } } +fn write_padding(writer: &mut impl Write, byte: u8, mut len: usize) -> std::io::Result<()> { + const BUFFER_SIZE: usize = 1024; + let buffer = [byte; BUFFER_SIZE]; + + while len >= BUFFER_SIZE { + writer.write_all(&buffer)?; + len -= BUFFER_SIZE; + } + writer.write_all(&buffer[..len]) +} + fn write_output( mut writer: impl Write, sign_indicator: String, @@ -769,17 +780,18 @@ fn write_output( // Check if the width is too large for formatting super::check_width(remaining_width)?; + let padding = width.saturating_sub(sign_indicator.len().saturating_add(s.len())); + match alignment { - NumberAlignment::Left => write!(writer, "{sign_indicator}{s: { + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(s.as_bytes())?; + write_padding(&mut writer, b' ', padding) + } NumberAlignment::RightSpace => { - let is_sign = sign_indicator.starts_with('-') || sign_indicator.starts_with('+'); // When sign_indicator is in ['-', '+'] - if is_sign && remaining_width > 0 { - // Make sure sign_indicator is just next to number, e.g. "% +5.1f" 1 ==> $ +1.0 - let s = sign_indicator + s.as_str(); - write!(writer, "{s:>width$}", width = remaining_width + 1) // Since we now add sign_indicator and s together, plus 1 - } else { - write!(writer, "{sign_indicator}{s:>remaining_width$}") - } + write_padding(&mut writer, b' ', padding)?; + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(s.as_bytes()) } NumberAlignment::RightZero => { // Add the padding after "0x" for hexadecimals @@ -788,8 +800,10 @@ fn write_output( } else { ("", s.as_str()) }; - let remaining_width = remaining_width.saturating_sub(prefix.len()); - write!(writer, "{sign_indicator}{prefix}{rest:0>remaining_width$}") + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(prefix.as_bytes())?; + write_padding(&mut writer, b'0', padding)?; + writer.write_all(rest.as_bytes()) } } } diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index fda7f7d158e..a9ea924e35c 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1528,6 +1528,18 @@ fn test_large_width_format() { } } +#[test] +fn test_numeric_field_width_above_u16_max() { + const WIDTH: usize = 65_536; + + let result = new_ucmd!().args(&["%65536d", "5"]).succeeds(); + let stdout = result.stdout(); + + assert_eq!(stdout.len(), WIDTH); + assert!(stdout[..WIDTH - 1].iter().all(|&byte| byte == b' ')); + assert_eq!(stdout[WIDTH - 1], b'5'); +} + #[test] fn test_extreme_field_width_overflow() { // Test the specific case that was causing panic due to integer overflow From bc89833a1c311bf3323f74fd72371014adae1d29 Mon Sep 17 00:00:00 2001 From: Arihan Yadav Date: Tue, 11 Aug 2026 08:27:26 -0700 Subject: [PATCH 2/2] printf: document padding chunk size --- src/uucore/src/lib/features/format/num_format.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 9314f35fd0d..0350a268d6e 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -750,6 +750,7 @@ fn strip_fractional_zeroes_and_dot(s: &mut String) { } fn write_padding(writer: &mut impl Write, byte: u8, mut len: usize) -> std::io::Result<()> { + // One KiB keeps stack use small while amortizing writes for large field widths. const BUFFER_SIZE: usize = 1024; let buffer = [byte; BUFFER_SIZE];