diff --git a/src/uu/date/src/format_modifiers.rs b/src/uu/date/src/format_modifiers.rs index b77ed78dd65..22fef5fe971 100644 --- a/src/uu/date/src/format_modifiers.rs +++ b/src/uu/date/src/format_modifiers.rs @@ -483,6 +483,13 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result Result { - let target_len = - current_len - .checked_add(padding) - .ok_or_else(|| FormatError::FieldWidthTooLarge { - width, - specifier: specifier.to_string(), - })?; + let too_large = || FormatError::FieldWidthTooLarge { + width, + specifier: specifier.to_string(), + }; + let target_len = current_len.checked_add(padding).ok_or_else(too_large)?; + if target_len > MAX_FIELD_WIDTH { + return Err(too_large()); + } let mut s = String::new(); - s.try_reserve(target_len) - .map_err(|_| FormatError::FieldWidthTooLarge { - width, - specifier: specifier.to_string(), - })?; + s.try_reserve(target_len).map_err(|_| too_large())?; Ok(s) } @@ -869,6 +873,27 @@ mod tests { )); } + #[test] + fn test_format_with_modifiers_huge_width_does_not_allocate() { + // A width that fits in `usize` but is absurdly large must be rejected + // up front rather than attempting a multi-terabyte allocation (which + // aborts under a sanitizer). Regression test for issue #12458. + let date = make_test_date(1999, 6, 1, 0); + let config = get_config(); + for format in ["%6666666666666D", "%+6666666666666D", "%8888888888888q"] { + let err = format_with_modifiers(&date, format, &config).unwrap_err(); + assert!(matches!(err, FormatError::FieldWidthTooLarge { .. })); + } + } + + #[test] + fn test_try_alloc_padded_boundary() { + // A target length exactly at the cap is allowed; one byte over is rejected. + assert!(try_alloc_padded(0, MAX_FIELD_WIDTH, MAX_FIELD_WIDTH, "Y").is_ok()); + let err = try_alloc_padded(1, MAX_FIELD_WIDTH, MAX_FIELD_WIDTH, "Y").unwrap_err(); + assert!(matches!(err, FormatError::FieldWidthTooLarge { .. })); + } + #[test] fn test_underscore_flag_without_width() { // %_m should pad month to default width 2 with spaces diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index f61c99cad26..4d10b936215 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -361,6 +361,15 @@ fn test_date_format_q() { scene.ucmd().arg("+%q").succeeds().stdout_matches(&re); } +#[test] +fn test_date_format_huge_width_fails_gracefully() { + // An absurdly large field width must be rejected with an error rather than + // attempting a multi-terabyte allocation. Regression test for issue #12458. + for fmt in ["+%6666666666666D", "+%+6666666666666D", "+%8888888888888q"] { + new_ucmd!().arg(fmt).fails().no_stdout(); + } +} + #[test] fn test_date_format_m() { let scene = TestScenario::new(util_name!());