From 7c6d1e77d6cf2cd767a783719cb9d4e971d92267 Mon Sep 17 00:00:00 2001 From: fixfon <12512757+fixfon@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:26:38 +0300 Subject: [PATCH] checksum: do not let improperly formatted lines select the line format --- .../src/lib/features/checksum/validate.rs | 88 ++++++++++++++++++- tests/by-util/test_cksum.rs | 43 +++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index 5b6be1e73d7..036de541494 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -862,10 +862,13 @@ fn process_checksum_line( // Use `LineInfo` to extract the data of a line. // Then, depending on its format, apply a different pre-treatment. - let line_info = LineInfo::parse(line, cached_line_format, opts.allow_empty_filename) + // The detected format is kept only if the line turns out to be properly + // formatted: an improper line must not decide how the next ones are read. + let mut line_format = *cached_line_format; + let line_info = LineInfo::parse(line, &mut line_format, opts.allow_empty_filename) .ok_or(LineCheckError::ImproperlyFormatted)?; - if line_info.format == LineFormat::AlgoBased { + let result = if line_info.format == LineFormat::AlgoBased { process_algo_based_line(&line_info, cli_algo_name, opts, last_algo) } else if let Some(cli_algo) = cli_algo_name { // If we match a non-algo based parser, we expect a cli argument @@ -874,7 +877,12 @@ fn process_checksum_line( } else { // We have no clue of what algorithm to use Err(LineCheckError::ImproperlyFormatted) + }; + + if !matches!(result, Err(LineCheckError::ImproperlyFormatted)) { + *cached_line_format = line_format; } + result } fn process_checksum_file( @@ -1334,6 +1342,82 @@ mod tests { assert!(cached_line_format.is_none()); } + fn check_md5_line( + line: &str, + cached_line_format: &mut Option, + ) -> Result<(), LineCheckError> { + let opts = ChecksumValidateOptions { + ignore_missing: true, + ..Default::default() + }; + process_checksum_line( + OsStr::new(line), + Some(AlgoKind::Md5), + None, + opts, + cached_line_format, + &mut None, + ) + } + + #[test] + fn test_improperly_formatted_line_does_not_select_single_space_format() { + let mut cached_line_format = None; + + let res = check_md5_line("2024 release", &mut cached_line_format); + assert!(matches!(res, Err(LineCheckError::ImproperlyFormatted))); + assert_eq!(cached_line_format, None); + + let res = check_md5_line( + "d41d8cd98f00b204e9800998ecf8427e missing", + &mut cached_line_format, + ); + assert!(matches!(res, Err(LineCheckError::FileNotFound))); + assert_eq!(cached_line_format, Some(LineFormat::Untagged)); + } + + #[test] + fn test_improperly_formatted_line_does_not_select_untagged_format() { + let mut cached_line_format = None; + + let res = check_md5_line("Checksums for release", &mut cached_line_format); + assert!(matches!(res, Err(LineCheckError::ImproperlyFormatted))); + assert_eq!(cached_line_format, None); + + let res = check_md5_line( + "d41d8cd98f00b204e9800998ecf8427e missing", + &mut cached_line_format, + ); + assert!(matches!(res, Err(LineCheckError::FileNotFound))); + assert_eq!(cached_line_format, Some(LineFormat::SingleSpace)); + } + + #[test] + fn test_properly_formatted_line_selects_format_whatever_the_digest_check_outcome() { + let mut cached_line_format = None; + let res = check_md5_line( + "d41d8cd98f00b204e9800998ecf8427e missing", + &mut cached_line_format, + ); + assert!(matches!(res, Err(LineCheckError::FileNotFound))); + assert_eq!(cached_line_format, Some(LineFormat::Untagged)); + + let mut cached_line_format = None; + let res = check_md5_line( + "d41d8cd98f00b204e9800998ecf8427e Cargo.toml", + &mut cached_line_format, + ); + assert!(matches!(res, Err(LineCheckError::DigestMismatch))); + assert_eq!(cached_line_format, Some(LineFormat::SingleSpace)); + + let res = check_md5_line( + "d41d8cd98f00b204e9800998ecf8427e missing", + &mut cached_line_format, + ); + assert!(matches!(res, Err(LineCheckError::FileNotFound))); + assert_eq!(cached_line_format, Some(LineFormat::SingleSpace)); + } + #[test] fn test_get_expected_digest() { let ck = "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=".to_owned(); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index a6b478cff29..e9f57ef7368 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -3920,3 +3920,46 @@ fn test_numeric_looking_directory_names_are_kept_verbatim() { .fails_with_code(1) .stderr_is("cksum: 007: Is a directory\ncksum: 1e3: Is a directory\n"); } + +#[test] +fn test_check_improperly_formatted_line_does_not_select_format() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // The listed files were replaced, their genuine content is kept under the + // same names with a leading space. + for f in ["a", "c"] { + at.write(f, "tampered\n"); + at.write(&format!(" {f}"), &format!("{f}\n")); + } + at.write( + "check.md5sum", + "2024 release\n\ + 60b725f10c9c85c70d97880dfe8191b3 a\n\ + 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 c\n", + ); + scene + .ucmd() + .arg("--check") + .arg("--algorithm=md5") + .arg("check.md5sum") + .fails_with_code(1) + .stdout_is("a: FAILED\nc: FAILED\n") + .stderr_contains("1 line is improperly formatted") + .stderr_contains("2 computed checksums did NOT match"); + + at.write("a", "a\n"); + at.write( + "check_single_space.md5sum", + "Checksums for release\n\ + 60b725f10c9c85c70d97880dfe8191b3 a\n", + ); + scene + .ucmd() + .arg("--check") + .arg("--algorithm=md5") + .arg("check_single_space.md5sum") + .succeeds() + .stdout_is("a: OK\n") + .stderr_contains("1 line is improperly formatted"); +}