tr: fix possible usage in invalid utf8 set sequence. (#10791)

This commit is contained in:
David CARLIER
2026-02-08 11:18:01 +01:00
committed by GitHub
parent 2a4573326f
commit 99fa1bb9c5
4 changed files with 36 additions and 7 deletions
+1
View File
@@ -23,6 +23,7 @@ tr-error-write-error = write error
# Warning messages
tr-warning-unescaped-backslash = warning: an unescaped backslash at end of string is not portable
tr-warning-ambiguous-octal-escape = the ambiguous octal escape \{ $origin_octal } is being interpreted as the 2-byte sequence \0{ $actual_octal_tail }, { $outstand_char }
tr-warning-invalid-utf8 = invalid utf8 sequence
# Sequence parsing error messages
tr-error-missing-char-class-name = missing character class name '[::]'
+1
View File
@@ -24,6 +24,7 @@ tr-error-write-error = erreur d'écriture
tr-warning-unescaped-backslash = avertissement : une barre oblique inverse non échappée à la fin de la chaîne n'est pas portable
tr-warning-ambiguous-octal-escape = l'échappement octal ambigu \{ $origin_octal } est en cours
d'interprétation comme la séquence de 2 octets \0{ $actual_octal_tail }, { $outstand_char }
tr-warning-invalid-utf8 = séquence UTF-8 non valide
# Messages d'erreur d'analyse de séquence
tr-error-missing-char-class-name = nom de classe de caractères manquant '[::]'
+10 -7
View File
@@ -379,13 +379,16 @@ impl Sequence {
let str_to_parse = std::str::from_utf8(out).unwrap();
let result = u8::from_str_radix(str_to_parse, 8).ok();
if result.is_none() {
let origin_octal: &str = std::str::from_utf8(input).unwrap();
let actual_octal_tail: &str = std::str::from_utf8(&input[0..2]).unwrap();
let outstand_char: char = char::from_u32(input[2] as u32).unwrap();
show_warning!(
"{}",
translate!("tr-warning-ambiguous-octal-escape", "origin_octal" => origin_octal, "actual_octal_tail" => actual_octal_tail, "outstand_char" => outstand_char)
);
if let Ok(origin_octal) = std::str::from_utf8(input) {
let actual_octal_tail: &str = std::str::from_utf8(&input[0..2]).unwrap();
let outstand_char: char = char::from_u32(input[2] as u32).unwrap();
show_warning!(
"{}",
translate!("tr-warning-ambiguous-octal-escape", "origin_octal" => origin_octal, "actual_octal_tail" => actual_octal_tail, "outstand_char" => outstand_char)
);
} else {
show_warning!("{}", translate!("tr-warning-invalid-utf8"));
}
}
result
},
+24
View File
@@ -1544,6 +1544,30 @@ fn test_non_digit_repeat() {
.stderr_only("tr: invalid repeat count 'c' in [c*n] construct\n");
}
#[test]
#[cfg(unix)]
fn test_octal_escape_ambiguous_followed_by_non_utf8() {
// This case does not trigger the panic
let set1 = OsStr::from_bytes(b"\\501a");
new_ucmd!()
.arg("-d")
.arg(set1)
.pipe_in("(1a)")
.succeeds()
.stderr_contains("warning: the ambiguous octal escape")
.stdout_is(")");
// An user is not supposed to use this invalid utf8 set but
// we would need to make the command more error-proof still
let set1 = OsStr::from_bytes(b"\\501\xff");
new_ucmd!()
.arg("-d")
.arg(set1)
.pipe_in([b'(', b'1', 0xff, b')'])
.succeeds()
.stderr_contains("warning: invalid utf8 sequence");
}
#[cfg(target_os = "linux")]
#[test]
fn test_failed_write_is_reported() {