From 81e44670afb39022578972c50221f53ba1ca9416 Mon Sep 17 00:00:00 2001 From: weili <541602953@qq.com> Date: Fri, 14 Aug 2026 04:33:15 +0000 Subject: [PATCH 1/2] numfmt: fix char-boundary panic on a multibyte locale decimal separator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `find_valid_number_with_suffix` located the suffix with `s.chars().skip(numeric_part.len())`, using `numeric_part.len()` (a byte length) as a char-skip count. When the numeric part holds a multibyte char — e.g. the Arabic decimal separator `٫` (U+066B) under `LC_ALL=ar_SA.UTF-8`, so `1٫€K` parses `1٫` as the numeric part — the byte count skips past the following multibyte `€` onto a later valid suffix, taking a slicing arm; then `&s[..=numeric_part.len()]` cuts `€` mid-char and aborts instead of reporting the invalid suffix like GNU. Index the suffix from the byte offset `numeric_part.len()` (already a char boundary) instead of skipping by chars, so the real next char is examined and an invalid multibyte suffix takes the graceful rejection path. Fixes #13937 --- src/uu/numfmt/src/format.rs | 2 +- tests/by-util/test_numfmt.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 77febdaaa41..e2cb093dea1 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -57,7 +57,7 @@ fn find_valid_number_with_suffix(s: &str, unit: Unit) -> Option<&str> { let accepts_suffix = unit != Unit::None; let accepts_i = [Unit::Auto, Unit::Iec(true)].contains(&unit); - let mut characters = s.chars().skip(numeric_part.len()); + let mut characters = s[numeric_part.len()..].chars(); let potential_suffix = characters.next(); let potential_i = characters.next(); diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 33d810af269..77e0d2c495d 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -447,6 +447,21 @@ fn test_field_with_multibyte_whitespace_separator() { .stdout_only("1K 2000\n"); } +#[test] +fn test_from_multibyte_decimal_separator_invalid_suffix() { + new_ucmd!() + .env("LC_ALL", "ar_SA.UTF-8") + .args(&["--from=si", "1٫€K"]) + .fails_with_code(2) + .stderr_only("numfmt: invalid suffix in input '1٫€K': '€K'\n"); + + new_ucmd!() + .env("LC_ALL", "ar_SA.UTF-8") + .args(&["--from=auto", "1٫€Ki"]) + .fails_with_code(2) + .stderr_only("numfmt: invalid suffix in input '1٫€Ki': '€Ki'\n"); +} + #[test] fn test_format_selected_fields() { new_ucmd!() From 96e37c8df8c241f694e3c5e1d00d1840eeea0e88 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 14 Aug 2026 07:41:48 +0200 Subject: [PATCH 2/2] Should fail with French too We have the locale bullt Co-authored-by: Sylvestre Ledru --- tests/by-util/test_numfmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 77e0d2c495d..ea731dde3e3 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -450,7 +450,7 @@ fn test_field_with_multibyte_whitespace_separator() { #[test] fn test_from_multibyte_decimal_separator_invalid_suffix() { new_ucmd!() - .env("LC_ALL", "ar_SA.UTF-8") + .env("LC_ALL", "fr_FR. UTF-8") .args(&["--from=si", "1٫€K"]) .fails_with_code(2) .stderr_only("numfmt: invalid suffix in input '1٫€K': '€K'\n");