From 7b47d581d544650e160671599567e5a0433511d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 20 Nov 2025 01:39:14 +0100 Subject: [PATCH 1/2] checksum: fix GNU test cksum-base64-untagged.sh --- .../src/lib/features/checksum/validate.rs | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index b68032925ec..6b0595a42d0 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore rsplit hexdigit bitlen bytelen invalidchecksum xffname +// spell-checker:ignore rsplit hexdigit bitlen bytelen invalidchecksum inva idchecksum xffname use std::borrow::Cow; use std::ffi::OsStr; @@ -296,27 +296,7 @@ impl LineFormat { SubCase::OpenSSL => ByteSliceExt::rsplit_once(after_paren, b")= ")?, }; - fn is_valid_checksum(checksum: &[u8]) -> bool { - if checksum.is_empty() { - return false; - } - - let mut parts = checksum.splitn(2, |&b| b == b'='); - let main = parts.next().unwrap(); // Always exists since checksum isn't empty - let padding = parts.next().unwrap_or_default(); // Empty if no '=' - - main.iter() - .all(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/') - && !main.is_empty() - && padding.len() <= 2 - && padding.iter().all(|&b| b == b'=') - } - if !is_valid_checksum(checksum) { - return None; - } - // SAFETY: we just validated the contents of checksum, we can unsafely make a - // String from it - let checksum_utf8 = unsafe { String::from_utf8_unchecked(checksum.to_vec()) }; + let checksum_utf8 = Self::validate_checksum_format(checksum)?; Some(LineInfo { algo_name: Some(algo_utf8), @@ -336,12 +316,8 @@ impl LineFormat { fn parse_untagged(line: &[u8]) -> Option { let space_idx = line.iter().position(|&b| b == b' ')?; let checksum = &line[..space_idx]; - if !checksum.iter().all(|&b| b.is_ascii_hexdigit()) || checksum.is_empty() { - return None; - } - // SAFETY: we just validated the contents of checksum, we can unsafely make a - // String from it - let checksum_utf8 = unsafe { String::from_utf8_unchecked(checksum.to_vec()) }; + + let checksum_utf8 = Self::validate_checksum_format(checksum)?; let rest = &line[space_idx..]; let filename = rest @@ -388,6 +364,34 @@ impl LineFormat { format: Self::SingleSpace, }) } + + /// Ensure that the given checksum is syntactically valid (that it is either + /// hexadecimal or base64 encoded). + fn validate_checksum_format(checksum: &[u8]) -> Option { + if checksum.is_empty() { + return None; + } + + let mut parts = checksum.splitn(2, |&b| b == b'='); + let main = parts.next().unwrap(); // Always exists since checksum isn't empty + let padding = parts.next().unwrap_or_default(); // Empty if no '=' + + if main.is_empty() + || !main + .iter() + .all(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/') + { + return None; + } + + if padding.len() > 2 || padding.iter().any(|&b| b != b'=') { + return None; + } + + // SAFETY: we just validated the contents of checksum, we can unsafely make a + // String from it + Some(unsafe { String::from_utf8_unchecked(checksum.to_vec()) }) + } } // Helper trait for byte slice operations @@ -1039,7 +1043,13 @@ mod tests { b"b064a020db8018f18ff5ae367d01b212 ", Some((b"b064a020db8018f18ff5ae367d01b212", b" ")), ), - (b"invalidchecksum test", None), + // base64 checksums are accepted + ( + b"b21lbGV0dGUgZHUgZnJvbWFnZQ== ", + Some((b"b21lbGV0dGUgZHUgZnJvbWFnZQ==", b" ")), + ), + // Invalid checksums fail + (b"inva|idchecksum test", None), ]; for (input, expected) in test_cases { From b8cef3c2db140460f818b02d4df926be9864be53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 20 Nov 2025 02:20:00 +0100 Subject: [PATCH 2/2] test(cksum): Implement the GNU cksum-base64-untagged.sh test --- tests/by-util/test_cksum.rs | 113 ++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 4b9459ef5c8..57f11b8ef39 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -2319,6 +2319,119 @@ mod gnu_cksum_base64 { } } +/// This module reimplements the cksum-base64-untagged.sh GNU test. +mod gnu_cksum_base64_untagged { + use super::*; + + macro_rules! decl_sha_test { + ($id:ident, $algo:literal, $len:expr) => { + mod $id { + use super::*; + + #[test] + fn check_length_guess() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("inp", "test input\n"); + + let compute = ts + .ucmd() + .arg("-a") + .arg($algo) + .arg("-l") + .arg(stringify!($len)) + .arg("--base64") + .arg("--untagged") + .arg("inp") + .succeeds(); + + at.write_bytes("check", compute.stdout()); + + ts.ucmd() + .arg("-a") + .arg($algo) + .arg("--check") + .arg("check") + .succeeds() + .stdout_only("inp: OK\n"); + + at.write("check", " inp"); + + ts.ucmd() + .arg("-a") + .arg($algo) + .arg("check") + .fails() + .stderr_contains(concat!( + "--algorithm=", + $algo, + " requires specifying --length" + )); + } + } + }; + } + + decl_sha_test!(sha2_224, "sha2", 224); + decl_sha_test!(sha2_256, "sha2", 256); + decl_sha_test!(sha2_384, "sha2", 384); + decl_sha_test!(sha2_512, "sha2", 512); + decl_sha_test!(sha3_224, "sha3", 224); + decl_sha_test!(sha3_256, "sha3", 256); + decl_sha_test!(sha3_384, "sha3", 384); + decl_sha_test!(sha3_512, "sha3", 512); + + macro_rules! decl_blake_test { + ($id:ident, $len:expr) => { + mod $id { + use super::*; + + #[test] + fn check_length_guess() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("inp", "test input\n"); + + let compute = ts + .ucmd() + .arg("-a") + .arg("blake2b") + .arg("-l") + .arg(stringify!($len)) + .arg("--base64") + .arg("--untagged") + .arg("inp") + .succeeds(); + + at.write_bytes("check", compute.stdout()); + + ts.ucmd() + .arg("-a") + .arg("blake2b") + .arg("--check") + .arg("check") + .succeeds() + .stdout_only("inp: OK\n"); + } + } + }; + } + + decl_blake_test!(blake2b_8, 8); + decl_blake_test!(blake2b_216, 216); + decl_blake_test!(blake2b_224, 224); + decl_blake_test!(blake2b_232, 232); + decl_blake_test!(blake2b_248, 248); + decl_blake_test!(blake2b_256, 256); + decl_blake_test!(blake2b_264, 264); + decl_blake_test!(blake2b_376, 376); + decl_blake_test!(blake2b_384, 384); + decl_blake_test!(blake2b_392, 392); + decl_blake_test!(blake2b_504, 504); + decl_blake_test!(blake2b_512, 512); +} /// This module reimplements the cksum-c.sh GNU test. mod gnu_cksum_c { use super::*;