From d965cfd0a81c656525d2fc3d76df13732e1f7517 Mon Sep 17 00:00:00 2001 From: Han Damin Date: Mon, 15 Jun 2026 10:40:20 +0900 Subject: [PATCH] fix(dd): reject oversized block sizes instead of crashing A huge ibs=/obs=/bs=/cbs= value used to panic (multiply overflow) or abort (failed allocation). Reject block sizes >= i64::MAX at parse time like GNU, and allocate the output buffer with try_reserve. Fixes #12844, #12847 --- src/uu/dd/src/bufferedoutput.rs | 18 +++++++++--------- src/uu/dd/src/dd.rs | 7 ++++--- src/uu/dd/src/parseargs.rs | 8 ++++++++ tests/by-util/test_dd.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs index cd16eb0e701..7f4f92b8528 100644 --- a/src/uu/dd/src/bufferedoutput.rs +++ b/src/uu/dd/src/bufferedoutput.rs @@ -30,13 +30,13 @@ impl<'a> BufferedOutput<'a> { /// Add partial block buffering to the given block writer. /// /// The internal buffer size is at most the value of `obs` as - /// defined in `inner`. - pub(crate) fn new(inner: Output<'a>) -> Self { + /// defined in `inner`. `obs` may be huge, so the allocation can fail + /// without aborting: an oversized `obs` returns an error (like GNU `dd`). + pub(crate) fn new(inner: Output<'a>) -> std::io::Result { let obs = inner.settings.obs; - Self { - inner, - buf: Vec::with_capacity(obs), - } + let mut buf = Vec::new(); + buf.try_reserve(obs)?; + Ok(Self { inner, buf }) } pub(crate) fn discard_cache(&self, offset: u64, len: u64) { @@ -118,7 +118,7 @@ mod tests { dst: Dest::Sink, settings: &settings, }; - let mut output = BufferedOutput::new(inner); + let mut output = BufferedOutput::new(inner).unwrap(); let wstat = output.write_blocks(&[]).unwrap(); assert_eq!(wstat.writes_complete, 0); assert_eq!(wstat.writes_partial, 0); @@ -136,7 +136,7 @@ mod tests { dst: Dest::Sink, settings: &settings, }; - let mut output = BufferedOutput::new(inner); + let mut output = BufferedOutput::new(inner).unwrap(); let wstat = output.write_blocks(b"ab").unwrap(); assert_eq!(wstat.writes_complete, 0); assert_eq!(wstat.writes_partial, 0); @@ -154,7 +154,7 @@ mod tests { dst: Dest::Sink, settings: &settings, }; - let mut output = BufferedOutput::new(inner); + let mut output = BufferedOutput::new(inner).unwrap(); let wstat = output.write_blocks(b"abcd").unwrap(); assert_eq!(wstat.writes_complete, 1); assert_eq!(wstat.writes_partial, 0); diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 782d2664c21..0599963e6e3 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -1202,7 +1202,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { // Add partial block buffering, if needed. let mut o = if o.settings.buffered { - BlockWriter::Buffered(BufferedOutput::new(o)) + BlockWriter::Buffered(BufferedOutput::new(o)?) } else { BlockWriter::Unbuffered(o) }; @@ -1411,8 +1411,9 @@ fn read_helper(i: &mut Input, buf: &mut Vec, bsize: usize) -> io::Result usize { let gcd = Gcd::gcd(ibs, obs); - // calculate the lcm from gcd - (ibs / gcd) * obs + // calculate the lcm from gcd; saturate so an oversized product fails at + // allocation instead of panicking here + (ibs / gcd).saturating_mul(obs) } /// Calculate the buffer size appropriate for this loop iteration, respecting diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index a2bc1103651..cd20771c185 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -325,6 +325,14 @@ impl Parser { if num == 0 { return Err(ParseError::InvalidNumber(val.to_string())); } + // GNU rejects a block size >= i64::MAX (intmax_t); otherwise the value + // overflows later when the buffer is computed or allocated. + if num >= i64::MAX as u64 { + return Err(ParseError::InvalidNumberWithErrMsg( + val.to_string(), + "Value too large for defined data type".to_string(), + )); + } num.try_into() .map_err(|_| ParseError::BsOutOfRange(arg.to_string())) } diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 309a93ac020..246b0a29242 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -132,6 +132,35 @@ fn test_out_of_memory_skip() { .stderr_contains("memory"); } +#[test] +fn test_huge_block_size_is_rejected_without_panicking() { + // Regression test for #12844: a block size >= i64::MAX used to panic + // ("attempt to multiply with overflow"); it must be rejected cleanly. + for arg in [ + "bs=9223372036854775807", // i64::MAX + "ibs=99999999999999999999", // larger than u64::MAX + "obs=18446744073709551616", // u64::MAX + 1 + "cbs=9223372036854775808", // i64::MAX + 1 + "ibs=1778172772721772786161B", // overflows via the 'B' suffix + ] { + 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 + // ("memory allocation of N bytes failed"); it must fail gracefully instead. + new_ucmd!() + .arg("obs=1PB") + .fails_with_code(1) + .stderr_contains("memory"); +} + #[test] fn test_stdin_stdout() { let input = build_ascii_block(521);