diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 0599963e6e3..6ee4c0208af 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -161,7 +161,10 @@ impl Num { fn to_bytes(self, block_size: u64) -> u64 { match self { - Self::Blocks(n) => n * block_size, + // Saturate on overflow so a block count that exceeds u64 is caught + // by the i64::MAX bound check in `Parser::validate` rather than + // wrapping past it. + Self::Blocks(n) => n.saturating_mul(block_size), Self::Bytes(n) => n, } } @@ -1429,7 +1432,7 @@ fn calc_loop_bsize( Some(Num::Blocks(rmax)) => { let rsofar = rstat.reads_complete + rstat.reads_partial; let rremain = rmax - rsofar; - cmp::min(ideal_bsize as u64, rremain * ibs as u64) as usize + cmp::min(ideal_bsize as u64, rremain.saturating_mul(ibs as u64)) as usize } Some(Num::Bytes(bmax)) => { let bmax: u128 = bmax.into(); diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index cd20771c185..70e3ae79cba 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -655,4 +655,19 @@ mod tests { assert!(matches!(Parser::parse_n(arg), Ok(Num::Bytes(_)))); } } + + #[test] + fn test_skip_seek_block_count_byte_overflow_rejected() { + // A skip/seek in blocks is multiplied by the block size (default 512) + // before the i64::MAX bound check. 2^55 blocks * 512 == 2^64, which + // wrapped past that check to a small value (and panicked in debug). + // It must be rejected as too large instead. + use crate::parseargs::ParseError; + for operand in ["skip=36028797018963968", "seek=36028797018963968"] { + assert!(matches!( + Parser::new().parse([operand]), + Err(ParseError::InvalidNumberWithErrMsg(_, _)) + )); + } + } } diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 849c47e227f..829c53118f1 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -146,6 +146,21 @@ fn test_huge_block_size_is_rejected_without_panicking() { } } +#[test] +fn test_huge_skip_seek_block_count_is_rejected_without_panicking() { + // A skip/seek given in blocks is multiplied by the block size (default + // 512) before the i64::MAX bound check. 2^55 blocks * 512 == 2^64 used to + // wrap past that check (panicking in debug builds, silently skipping + // nothing in release builds); it must be rejected cleanly instead. + for arg in ["skip=36028797018963968", "seek=36028797018963968"] { + new_ucmd!() + .arg(arg) + .fails_with_code(1) + .no_stdout() + .stderr_contains("Value too large for defined data type"); + } +} + #[test] fn test_huge_obs_reports_memory_error_instead_of_aborting() { // Regression test for #12847: a valid but huge `obs` used to abort