Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/uucore/src/lib/features/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,8 @@ 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.
/// Maximum padding width for formatting to prevent pathological output requests.
/// This limit is somewhat arbitrary but should be well above any practical use case.
const MAX_FORMAT_WIDTH: usize = 1_000_000;

/// Check if a width is too large for formatting.
Expand All @@ -146,6 +144,19 @@ fn check_width(width: usize) -> std::io::Result<()> {
}
}

fn write_padding(mut writer: impl Write, byte: u8, len: usize) -> std::io::Result<()> {
check_width(len)?;

let buffer = [byte; 1024];
let mut remaining = len;
while remaining > 0 {
let count = remaining.min(buffer.len());
writer.write_all(&buffer[..count])?;
remaining -= count;
}
Ok(())
}

/// A single item to format
pub enum FormatItem<C: FormatChar> {
/// A format specifier
Expand Down
27 changes: 19 additions & 8 deletions src/uucore/src/lib/features/format/num_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,19 +724,26 @@ fn write_output(
// 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::Left => {
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(s.as_bytes())?;
let padding = remaining_width.saturating_sub(s.len());
super::write_padding(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
let padding = width.saturating_sub(sign_indicator.len().saturating_add(s.len()));
super::write_padding(&mut writer, b' ', padding)?;
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(s.as_bytes())
} else {
write!(writer, "{sign_indicator}{s:>remaining_width$}")
writer.write_all(sign_indicator.as_bytes())?;
let padding = remaining_width.saturating_sub(s.len());
super::write_padding(&mut writer, b' ', padding)?;
writer.write_all(s.as_bytes())
}
}
NumberAlignment::RightZero => {
Expand All @@ -747,7 +754,11 @@ fn write_output(
("", 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())?;
let padding = remaining_width.saturating_sub(rest.len());
super::write_padding(&mut writer, b'0', padding)?;
writer.write_all(rest.as_bytes())
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions src/uucore/src/lib/features/format/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,11 @@ fn write_padded(
) -> 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$}", "")
super::write_padding(writer, b' ', padlen)
} else {
write!(writer, "{: >padlen$}", "")?;
super::write_padding(&mut writer, b' ', padlen)?;
writer.write_all(text)
}
.map_err(FormatError::IoError)
Expand Down
23 changes: 23 additions & 0 deletions tests/by-util/test_printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,29 @@ fn test_large_width_format() {
}
}

#[test]
fn test_large_but_allowed_width_format() {
let width = 111_111;

let expected_char = format!("{}x", " ".repeat(width - 1));
new_ucmd!()
.args(&["%111111c", "x"])
.succeeds()
.stdout_only(expected_char);

let expected_int = format!("{}1", " ".repeat(width - 1));
new_ucmd!()
.args(&["%111111.1d", "1"])
.succeeds()
.stdout_only(expected_int);

let expected_float = format!("{}1.0", " ".repeat(width - 3));
new_ucmd!()
.args(&["%111111.1f", "1"])
.succeeds()
.stdout_only(expected_float);
}

#[test]
fn test_extreme_field_width_overflow() {
// Test the specific case that was causing panic due to integer overflow
Expand Down
Loading