date: reject excessive format widths (#12497)

* date: reject excessive format widths

* date: relax huge width diagnostic test

* date: cap format modifier widths

---------

Co-authored-by: Deepak kudi <deepakkudi23@Deepaks-MacBook-Air.local>
Co-authored-by: Deepak kudi <deepakkudi23@adsl-172-10-9-116.dsl.sndg02.sbcglobal.net>
Co-authored-by: Puneet Dixit <puneetdixit200@users.noreply.github.com>
This commit is contained in:
Puneet Dixit
2026-06-06 12:19:11 +05:30
committed by GitHub
parent 7f791b9cd7
commit b15dba29c4
2 changed files with 54 additions and 12 deletions
+41 -11
View File
@@ -38,6 +38,8 @@ use jiff::fmt::strtime::{BrokenDownTime, Config, PosixCustom};
use std::fmt;
use uucore::translate;
const MAX_FORMAT_WIDTH: usize = u16::MAX as usize;
/// Error type for format modifier operations
#[derive(Debug)]
pub enum FormatError {
@@ -70,6 +72,13 @@ impl From<jiff::Error> for FormatError {
}
}
fn field_width_too_large(width: usize, specifier: &str) -> FormatError {
FormatError::FieldWidthTooLarge {
width,
specifier: specifier.to_string(),
}
}
/// A parsed `%`-format specifier: `%[flags][width][:colons]<letter>`.
struct ParsedSpec<'a> {
/// Flag characters from `[_0^#+-]`.
@@ -422,6 +431,9 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result<String, Forma
None if underscore_flag || pad_char != default_pad => get_default_width(specifier),
None => 0,
};
if effective_width > MAX_FORMAT_WIDTH {
return Err(field_width_too_large(effective_width, specifier));
}
// When the requested width is narrower than the default formatted width, GNU first removes default padding and then reapplies the requested width.
if effective_width > 0 && effective_width < result.len() {
@@ -491,19 +503,16 @@ fn try_alloc_padded(
width: usize,
specifier: &str,
) -> Result<String, FormatError> {
let target_len =
current_len
.checked_add(padding)
.ok_or_else(|| FormatError::FieldWidthTooLarge {
width,
specifier: specifier.to_string(),
})?;
if width > MAX_FORMAT_WIDTH {
return Err(field_width_too_large(width, specifier));
}
let target_len = current_len
.checked_add(padding)
.ok_or_else(|| field_width_too_large(width, specifier))?;
let mut s = String::new();
s.try_reserve(target_len)
.map_err(|_| FormatError::FieldWidthTooLarge {
width,
specifier: specifier.to_string(),
})?;
.map_err(|_| field_width_too_large(width, specifier))?;
Ok(s)
}
@@ -869,6 +878,27 @@ mod tests {
));
}
#[test]
fn test_try_alloc_padded_rejects_width_above_supported_max() {
let err = try_alloc_padded(1, 1, MAX_FORMAT_WIDTH + 1, "s").unwrap_err();
assert!(matches!(
err,
FormatError::FieldWidthTooLarge { width, specifier }
if width == MAX_FORMAT_WIDTH + 1 && specifier == "s"
));
}
#[test]
fn test_apply_modifiers_rejects_width_above_review_cap() {
let width = u16::MAX as usize + 1;
let err = apply_modifiers("00", &spec("", Some(width), "S")).unwrap_err();
assert!(matches!(
err,
FormatError::FieldWidthTooLarge { width: rejected, specifier }
if rejected == width && specifier == "S"
));
}
#[test]
fn test_underscore_flag_without_width() {
// %_m should pad month to default width 2 with spaces
+13 -1
View File
@@ -2560,10 +2560,22 @@ fn test_date_format_modifier_huge_width_fails_without_abort() {
new_ucmd!().arg(&format).fails().code_is(1);
}
#[test]
fn test_date_format_modifier_parseable_huge_width_fails_without_hanging() {
// Target pointer width can affect the reported parsed width, so assert the
// stable diagnostic shape instead of the exact oversized literal.
new_ucmd!()
.arg("+%8888888888888s")
.fails()
.code_is(1)
.stderr_contains("format modifier width '")
.stderr_contains("' is too large for specifier '%s'");
}
#[test]
fn test_date_format_large_width_no_oom() {
// Regression: very large width like %8888888888r caused OOM.
// GNU caps width to i32::MAX; verify we don't crash.
// Cap supported widths well below that so we don't crash.
// Use a moderate width with a fixed date to check the code path works.
new_ucmd!()
.arg("-d")