From 8281564c4156923a55b523a9fe8fa3ed29df2e9c Mon Sep 17 00:00:00 2001 From: Oliver Kopcik Date: Mon, 31 Aug 2026 03:45:23 +0200 Subject: [PATCH] dd: stop at a zero factor in a multiplier expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A zero factor makes the whole multiplier expression zero, so the factors after it are never looked at. We parsed every factor up front instead, so a later one that does not fit in a u64 failed the whole argument: $ dd count=00x9999999999999999999999999999999999999999999999999999999999999 dd: invalid number: '00x999…': Value too large for defined data type That should copy zero blocks and exit successfully, which is what it did before 7f9b9a6f0 ("dd: reject a number that does not fit in u64") turned ParseSizeError::SizeTooBig into an error rather than u64::MAX. The zero factor case was not covered there. Return as soon as a factor parses to zero, so the rest is never parsed. A number that does not fit in a u64 on its own stays an error. The zero-multiplier warning is only a check on the literal text, so it is kept in its own pass and still reported once per "0" factor. Fixes #14160 Co-Authored-By: Claude Opus 5 --- src/uu/dd/src/parseargs.rs | 39 +++++++++++++++++++++++++++++++++++++- tests/by-util/test_dd.rs | 17 +++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index d0aa6d88e2e..552f1eb881f 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -576,12 +576,24 @@ pub fn parse_bytes_with_opt_multiplier(s: &str) -> Result { if parts.len() == 1 { parse_bytes_no_x(s, parts[0]) } else { - let mut total: u64 = 1; + // The warning is purely lexical, so it is reported for every "0" + // factor even though the parsing below stops at the first zero. for (i, part) in parts.iter().enumerate() { if *part == "0" && i != parts.len() - 1 { show_zero_multiplier_warning(); } + } + + let mut total: u64 = 1; + for part in &parts { let num = parse_bytes_no_x(s, part)?; + // GNU stops at a zero factor and never looks at the rest of the + // expression, so the remaining factors must not be parsed: one + // of them not fitting in a u64 would otherwise be reported as an + // error even though the result is already known to be zero. + if num == 0 { + return Ok(0); + } total = total .checked_mul(num) .ok_or_else(|| ParseError::InvalidNumber(s.to_string()))?; @@ -687,6 +699,31 @@ mod tests { 2 * 2 * (3 * 2) // (1 * 2) * (2 * 1) * (3 * 2) ); } + + #[test] + fn test_parse_bytes_with_opt_multiplier_zero_factor() { + // GNU stops at a zero factor and never looks at the rest of the + // expression, so a later factor that does not fit in a u64 must not + // turn this into an error. + assert_eq!(parse_bytes_with_opt_multiplier("0x5").unwrap(), 0); + assert_eq!(parse_bytes_with_opt_multiplier("00x5").unwrap(), 0); + assert_eq!( + parse_bytes_with_opt_multiplier(&format!("0x{BIG}")).unwrap(), + 0 + ); + assert_eq!( + parse_bytes_with_opt_multiplier(&format!("00x{BIG}")).unwrap(), + 0 + ); + // The zero does not have to be the leading factor. + assert_eq!( + parse_bytes_with_opt_multiplier(&format!("2x0x{BIG}")).unwrap(), + 0 + ); + // A trailing zero is still a zero result. + assert_eq!(parse_bytes_with_opt_multiplier("5x0").unwrap(), 0); + } + #[test] fn test_parse_n() { for arg in ["1x8x4", "1c", "123b", "123w"] { diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 943fdd4feaa..d7c0243f02c 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -277,6 +277,23 @@ fn test_x_multiplier() { .stdout_is("abcdef"); } +#[test] +fn test_zero_factor_skips_the_rest_of_the_multiplier() { + // A zero factor makes the whole expression zero, so the factor after it + // is never parsed and an oversized one must not be reported as an error. + for arg in ["count", "seek", "skip"] { + new_ucmd!() + .args(&[ + format!("{arg}=00x9999999999999999999999999999999999999999999999999999999999999") + .as_str(), + "status=none", + ]) + .pipe_in("") + .succeeds() + .no_output(); + } +} + #[test] fn test_zero_multiplier_warning() { for arg in ["count", "seek", "skip"] {