From 58bccfd6a7640308a3d12155dd96724f352987f5 Mon Sep 17 00:00:00 2001 From: Rudxain <76864299+Rudxain@users.noreply.github.com> Date: Sun, 14 Dec 2025 21:17:05 -0400 Subject: [PATCH 1/2] stty: fix: reject "+hex" in `parse_saved_state` --- src/uu/stty/src/stty.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index dc1019ce1ab..7548db19c03 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -532,9 +532,15 @@ fn parse_saved_state(arg: &str) -> Option> { // Validate all parts are non-empty valid hex let mut values = Vec::with_capacity(expected_parts); for (i, part) in parts.iter().enumerate() { + // `from_str_radix` doesn't document its behavior for this case, + // thus, we do this to guarantee stability if part.is_empty() { return None; // GNU rejects empty hex values } + // TO-DO: avoid `from_str_radix` + if part.as_bytes()[0] == b'+' { + return None; + } let val = u32::from_str_radix(part, 16).ok()?; // Control characters (indices 4+) must fit in u8 From 6651e6b538d74cda91adfca666f41b6d8d5aa114 Mon Sep 17 00:00:00 2001 From: Rudxain <76864299+Rudxain@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:45:55 -0400 Subject: [PATCH 2/2] stty: test `parse_saved_state` --- src/uu/stty/src/stty.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 7548db19c03..db922e82f61 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -1436,6 +1436,24 @@ mod tests { assert_eq!(parse_rows_cols("65537"), Some(1)); // wraps to 1 } + #[test] + fn test_sane_parse_saved_state() { + let expected_parts = 4 + nix::libc::NCCS; + let mut parts: Vec = ["00", "01", "ff", "7f"].map(str::to_string).into(); + parts.resize(expected_parts, "00".to_string()); + let input = parts.join(":"); + assert!(parse_saved_state(&input).is_some()); + } + + #[test] + fn test_parse_saved_state_no_plus() { + let expected_parts = 4 + nix::libc::NCCS; + let mut parts: Vec = ["+00", "00", "00", "00"].map(str::to_string).into(); + parts.resize(expected_parts, "00".to_string()); + let input = parts.join(":"); + assert_eq!(parse_saved_state(&input), None); + } + // Sane control character defaults #[test] fn test_get_sane_control_char_values() {