mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
printf: handle extremely large format widths gracefully to fix GNU test panic
This commit is contained in:
@@ -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<C: FormatChar> {
|
||||
/// A format specifier
|
||||
|
||||
@@ -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:<remaining_width$}"),
|
||||
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
|
||||
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$}")
|
||||
|
||||
@@ -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, "{: <padlen$}", "")
|
||||
|
||||
@@ -1461,3 +1461,24 @@ fn test_emoji_formatting() {
|
||||
.succeeds()
|
||||
.stdout_only("Status: Success 🚀 🎯 Count: 42\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_width_format() {
|
||||
// Test that extremely large width specifications fail gracefully with an error
|
||||
// rather than panicking. This tests the fix for the printf-surprise.sh GNU test.
|
||||
// When printf tries to format with a width of 20 million, it should return
|
||||
// an error message and exit code 1, not panic with exit code 101.
|
||||
let test_cases = [
|
||||
("%20000000f", "0"), // float formatting
|
||||
("%10000000s", "test"), // string formatting
|
||||
("%15000000d", "42"), // integer formatting
|
||||
];
|
||||
|
||||
for (format, arg) in test_cases {
|
||||
new_ucmd!()
|
||||
.args(&[format, arg])
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("write error")
|
||||
.stdout_is("");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user