From c8663b9112592527287da830ec28ac2304976112 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 3 Nov 2025 23:32:37 +0100 Subject: [PATCH] printf: handle extremely large format widths gracefully to fix GNU test panic --- src/uucore/src/lib/features/format/mod.rs | 21 ++++++++++++++++++- .../src/lib/features/format/num_format.rs | 8 +++++-- src/uucore/src/lib/features/format/spec.rs | 4 ++++ tests/by-util/test_printf.rs | 21 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 1741340c4a9..54ce9690d5c 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -113,7 +113,7 @@ impl Display for FormatError { Self::InvalidPrecision(precision) => write!(f, "invalid precision: '{precision}'"), // TODO: Error message below needs some work Self::WrongSpecType => write!(f, "wrong % directive type was given"), - Self::IoError(_) => write!(f, "io error"), + Self::IoError(_) => write!(f, "write error"), Self::NoMoreArguments => write!(f, "no more arguments"), Self::InvalidArgument(_) => write!(f, "invalid argument"), Self::MissingHex => write!(f, "missing hexadecimal number in escape"), @@ -127,6 +127,25 @@ impl Display for FormatError { } } +/// Maximum width for formatting to prevent memory allocation panics. +/// Rust's formatter will panic when trying to allocate memory for very large widths. +/// This limit is somewhat arbitrary but should be well above any practical use case +/// while still preventing formatter panics. +const MAX_FORMAT_WIDTH: usize = 1_000_000; + +/// Check if a width is too large for formatting. +/// Returns an error if the width exceeds MAX_FORMAT_WIDTH. +fn check_width(width: usize) -> std::io::Result<()> { + if width > MAX_FORMAT_WIDTH { + Err(std::io::Error::new( + std::io::ErrorKind::OutOfMemory, + "formatting width too large", + )) + } else { + Ok(()) + } +} + /// A single item to format pub enum FormatItem { /// A format specifier diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 16971baf68c..6b17fe2fbad 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -693,7 +693,7 @@ fn strip_fractional_zeroes_and_dot(s: &mut String) { fn write_output( mut writer: impl Write, sign_indicator: String, - mut s: String, + s: String, width: usize, alignment: NumberAlignment, ) -> std::io::Result<()> { @@ -706,13 +706,17 @@ fn write_output( // by storing remaining_width indicating the actual width needed. // Using min() because self.width could be 0, 0usize - 1usize should be avoided let remaining_width = width - min(width, sign_indicator.len()); + + // Check if the width is too large for formatting + super::check_width(remaining_width)?; + match alignment { NumberAlignment::Left => write!(writer, "{sign_indicator}{s: { 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 - s = sign_indicator + s.as_str(); + 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$}") diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 98d455994d2..138e28740f0 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -550,6 +550,10 @@ fn write_padded( left: bool, ) -> Result<(), FormatError> { let padlen = width.saturating_sub(text.len()); + + // Check if the padding length is too large for formatting + super::check_width(padlen).map_err(FormatError::IoError)?; + if left { writer.write_all(text)?; write!(writer, "{: