From 548086672ded867d2e6ee5bd7df61fc607697522 Mon Sep 17 00:00:00 2001 From: Navaneeth Yadamreddy Date: Thu, 27 Aug 2026 05:26:34 +0530 Subject: [PATCH] =?UTF-8?q?numfmt:=20fix=20panic=20on=20multibyte=20locale?= =?UTF-8?q?=20decimal=20separator=20(#13937)=20`find=5Fvalid=5Fnumber=5Fwi?= =?UTF-8?q?th=5Fsuffix`=20used=20`numeric=5Fpart.len()`=20(byte=20count)?= =?UTF-8?q?=20as=20a=20char=20count=20in=20`chars().skip()`,=20causing=20d?= =?UTF-8?q?esync=20when=20the=20numeric=20part=20contains=20multibyte=20ch?= =?UTF-8?q?aracters=20(e.g.=20Arabic=20`=D9=AB`=20separator).=20This=20led?= =?UTF-8?q?=20to=20slicing=20into=20a=20multibyte=20char=20boundary=20and?= =?UTF-8?q?=20panicking.=20Fixed=20by=20using=20`chars().count()`=20for=20?= =?UTF-8?q?char=20iteration=20and=20computing=20byte=20indices=20via=20`ch?= =?UTF-8?q?ar=5Findices().nth()`=20for=20string=20slicing.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/uu/numfmt/src/format.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 9c03801ef7..8d57d0a15c 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -57,7 +57,8 @@ 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 numeric_char_count = numeric_part.chars().count(); + let mut characters = s.chars().skip(numeric_char_count); let potential_suffix = characters.next(); let potential_i = characters.next(); @@ -65,15 +66,24 @@ fn find_valid_number_with_suffix(s: &str, unit: Unit) -> Option<&str> { return Some(numeric_part); } + // Calculate byte index from char index since numeric_part may contain + // multibyte characters (e.g. locale decimal separators) + let suffix_byte_idx = s + .char_indices() + .nth(numeric_char_count) + .map_or(s.len(), |(i, _)| i); + match (potential_suffix, potential_i) { - (Some(suffix), None) if RawSuffix::try_from(&suffix).is_ok() => { - Some(&s[..=numeric_part.len()]) - } (Some(suffix), Some('i')) if accepts_i && RawSuffix::try_from(&suffix).is_ok() => { - Some(&s[..numeric_part.len() + 2]) + let next_char_byte = s + .char_indices() + .nth(numeric_char_count + 2) + .map_or(s.len(), |(i, _)| i); + Some(&s[..next_char_byte]) } - (Some(suffix), Some(_)) if RawSuffix::try_from(&suffix).is_ok() => { - Some(&s[..=numeric_part.len()]) + (Some(suffix), None) | (Some(suffix), Some(_)) if RawSuffix::try_from(&suffix).is_ok() => { + let end = suffix_byte_idx + suffix.len_utf8(); + Some(&s[..end]) } _ => Some(numeric_part), }