From e032755888218cb332f6bcef2b1e56ab31a88813 Mon Sep 17 00:00:00 2001 From: weili <541602953@qq.com> Date: Fri, 5 Jun 2026 05:27:16 +0000 Subject: [PATCH] touch: fix char-boundary panic on a multibyte char in a -t timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `touch -t '€123456789'` aborted with "byte index N is not a char boundary". `parse_timestamp` routes timestamps whose char count is 10 or 13 through `prepend_century`, which byte-sliced `s[..2]` to read the YY digits; a leading multibyte char (e.g. '€') made byte index 2 land mid-UTF-8 and panic before the `parse::()` error could fire. Take the first two chars instead of byte-slicing, so a multibyte/non-digit prefix yields the invalid-date error (exit 1) like GNU instead of crashing. --- src/uu/touch/src/touch.rs | 5 ++++- tests/by-util/test_touch.rs | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 4d1c9772c59..3fe1dcea91c 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -741,7 +741,10 @@ fn parse_date(ref_zoned: Zoned, s: &str) -> Result { /// - 68 and before is interpreted as 20xx /// - 69 and after is interpreted as 19xx fn prepend_century(s: &str) -> UResult { - let first_two_digits = s[..2].parse::().map_err(|_| { + // Take the first two chars rather than byte-slicing `s[..2]`: a leading + // multibyte char would otherwise split a UTF-8 boundary and panic. + let first_two: String = s.chars().take(2).collect(); + let first_two_digits = first_two.parse::().map_err(|_| { USimpleError::new( 1, translate!("touch-error-invalid-date-ts-format", "date" => s.quote()), diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index fbabdb75c65..0f3921b17f9 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -978,6 +978,16 @@ fn test_touch_invalid_date_format() { .stderr_contains("touch: invalid date format '+1000000000000 years'"); } +#[test] +fn test_touch_invalid_timestamp_leading_multibyte_char() { + for ts in ["€123456789", "€23456789012"] { + new_ucmd!() + .args(&["-t", ts, "f"]) + .fails_with_code(1) + .stderr_only(format!("touch: invalid date ts format '{ts}'\n")); + } +} + #[test] #[cfg(not(target_os = "freebsd"))] fn test_touch_symlink_with_no_deref() {