cksum: make sure --check raises "improperly formatted" for length-guessing related issues

cksum: Fix --check failing when digest length is wrong on untagged line
This commit is contained in:
Dorian Péron
2026-03-26 00:18:10 +01:00
committed by Daniel Hofstetter
parent 812eb64c50
commit e104fc8cf4
3 changed files with 46 additions and 15 deletions
@@ -200,6 +200,22 @@ impl AlgoKind {
use AlgoKind::*;
matches!(self, Sysv | Bsd | Crc | Crc32b)
}
/// When checking untagged format lines, non-XOF non-legacy algorithms
/// should report "improperly formatted lines" if the digest length isn't
/// equivalent to this.
pub fn expected_digest_bit_len(self) -> Option<usize> {
match self {
Self::Md5 => Some(Md5::BIT_SIZE),
Self::Sm3 => Some(Sm3::BIT_SIZE),
Self::Sha1 => Some(Sha1::BIT_SIZE),
Self::Sha224 => Some(Sha224::BIT_SIZE),
Self::Sha256 => Some(Sha256::BIT_SIZE),
Self::Sha384 => Some(Sha384::BIT_SIZE),
Self::Sha512 => Some(Sha512::BIT_SIZE),
_ => None,
}
}
}
/// Holds a length for a SHA2 of SHA3 algorithm kind.
@@ -15,7 +15,7 @@ use std::io::{self, BufReader, Read, Write, stderr, stdin};
use os_display::Quotable;
use crate::checksum::{
AlgoKind, BlakeLength, ChecksumError, ReadingMode, SizedAlgoKind, digest_reader,
AlgoKind, BlakeLength, ChecksumError, ReadingMode, ShaLength, SizedAlgoKind, digest_reader,
parse_blake_length, unescape_filename,
};
use crate::error::{FromIo, UError, UIoError, UResult, USimpleError};
@@ -490,7 +490,7 @@ impl LineInfo {
}
/// Extract the expected digest from the checksum string and decode it
fn get_raw_expected_digest(checksum: &str, byte_len_hint: Option<usize>) -> Option<Vec<u8>> {
fn get_raw_expected_digest(checksum: &str, bit_len_hint: Option<usize>) -> Option<Vec<u8>> {
// If the length of the digest is not a multiple of 2, then it must be
// improperly formatted (1 byte is 2 hex digits, and base64 strings should
// always be a multiple of 4).
@@ -498,6 +498,8 @@ fn get_raw_expected_digest(checksum: &str, byte_len_hint: Option<usize>) -> Opti
return None;
}
let byte_len_hint = bit_len_hint.map(|n| n.div_ceil(8));
let checks_hint = |len| byte_len_hint.is_none_or(|hint| hint == len);
// If the length of the string matches the one to be expected (in case it's
@@ -741,23 +743,23 @@ fn process_algo_based_line(
) -> Result<(), LineCheckError> {
let filename_to_check = line_info.filename.as_slice();
let (algo_kind, algo_byte_len) =
identify_algo_name_and_length(line_info, cli_algo_kind, last_algo)?;
let (algo_kind, algo_len) = identify_algo_name_and_length(line_info, cli_algo_kind, last_algo)?;
// If the digest bitlen is known, we can check the format of the expected
// checksum with it.
let digest_char_length_hint = match (algo_kind, algo_byte_len) {
(AlgoKind::Blake2b | AlgoKind::Blake3, Some(byte_len)) => Some(byte_len),
(AlgoKind::Shake128 | AlgoKind::Shake256, Some(bit_len)) => Some(bit_len.div_ceil(8)),
(AlgoKind::Shake128, None) => Some(sum::Shake128::DEFAULT_BIT_SIZE.div_ceil(8)),
(AlgoKind::Shake256, None) => Some(sum::Shake256::DEFAULT_BIT_SIZE.div_ceil(8)),
let digest_bit_length_hint = match (algo_kind, algo_len) {
(AlgoKind::Blake2b | AlgoKind::Blake3, Some(byte_len)) => Some(byte_len * 8),
(AlgoKind::Shake128 | AlgoKind::Shake256, Some(bit_len)) => Some(bit_len),
(AlgoKind::Shake128, None) => Some(sum::Shake128::DEFAULT_BIT_SIZE),
(AlgoKind::Shake256, None) => Some(sum::Shake256::DEFAULT_BIT_SIZE),
_ => None,
};
let expected_checksum = get_raw_expected_digest(&line_info.checksum, digest_char_length_hint)
let expected_checksum = get_raw_expected_digest(&line_info.checksum, digest_bit_length_hint)
.ok_or(LineCheckError::ImproperlyFormatted)?;
let algo = SizedAlgoKind::from_unsized(algo_kind, algo_byte_len)?;
let algo = SizedAlgoKind::from_unsized(algo_kind, algo_len)
.map_err(|_| LineCheckError::ImproperlyFormatted)?;
compute_and_check_digest_from_file(filename_to_check, &expected_checksum, algo, opts)
}
@@ -779,7 +781,9 @@ fn process_non_algo_based_line(
// Remove the leading asterisk if present - only for the first line
filename_to_check = &filename_to_check[1..];
}
let expected_checksum = get_raw_expected_digest(&line_info.checksum, None)
let expected_digest_sum = cli_algo_kind.expected_digest_bit_len();
let expected_checksum = get_raw_expected_digest(&line_info.checksum, expected_digest_sum)
.ok_or(LineCheckError::ImproperlyFormatted)?;
// When a specific algorithm name is input, use it and use the provided
@@ -789,7 +793,11 @@ fn process_non_algo_based_line(
ak::Blake2b | ak::Blake3 => Some(expected_checksum.len()),
ak::Sha2 | ak::Sha3 => {
// multiplication by 8 to get the number of bits
Some(expected_checksum.len() * 8)
Some(
ShaLength::try_from(expected_checksum.len() * 8)
.map_err(|_| LineCheckError::ImproperlyFormatted)?
.as_usize(),
)
}
_ => cli_algo_length,
};
+9 -2
View File
@@ -176,6 +176,10 @@ impl Digest for Blake3 {
#[derive(Default)]
pub struct Sm3(sm3::Sm3);
impl Sm3 {
pub const BIT_SIZE: usize = 256;
}
impl Digest for Sm3 {
fn hash_update(&mut self, input: &[u8]) {
<sm3::Sm3 as sm3::Digest>::update(&mut self.0, input);
@@ -190,7 +194,7 @@ impl Digest for Sm3 {
}
fn output_bits(&self) -> usize {
256
Self::BIT_SIZE
}
}
@@ -367,6 +371,9 @@ impl Digest for SysV {
// Implements the Digest trait for sha2 / sha3 algorithms with fixed output
macro_rules! impl_digest_common {
($algo_type: ty, $size: literal) => {
impl $algo_type {
pub const BIT_SIZE: usize = $size;
}
impl Default for $algo_type {
fn default() -> Self {
Self(Default::default())
@@ -386,7 +393,7 @@ macro_rules! impl_digest_common {
}
fn output_bits(&self) -> usize {
$size
Self::BIT_SIZE
}
}
};