From 4e70d9a5c53256abc6f060dd722f9a414b08cc63 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:56:58 -0700 Subject: [PATCH] Assert the undersized-buffer guard on the chunked read path Drive::read and read_fua split any request larger than the transport's transfer limit into chunks and slice the caller's buffer by count * 2048. The up-front length check is the only thing between an undersized buffer and a "range end index out of range" panic out of a public API, and the comment above it records that this was once a live panic. Every existing read test stays on the single-chunk path, where an undersized buffer is already tolerated and returns Err(DiscRead), so the guard itself had no coverage at all and a mutation run flipped its arithmetic freely. The test drives a mock transport with a small transfer limit and asserts the two paths agree: an undersized buffer is an error either way, and behaviour on a caller mistake does not depend on the drive's transfer limit. Confirmed by hand that both the reported * -> + mutation and a < -> > flip now fail. --- src/drive/mod.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/drive/mod.rs b/src/drive/mod.rs index 1baf1ad..f0fad69 100644 --- a/src/drive/mod.rs +++ b/src/drive/mod.rs @@ -2054,6 +2054,43 @@ mod command_tests { } } + /// An undersized caller buffer must be rejected identically whether the + /// request fits in one transfer or has to be chunked. + /// + /// The chunk loop slices `buf` by `count * 2048`, so without the up-front + /// length check this PANICKED with "range end index out of range" out of + /// the public `read`/`read_fua` — while the single-chunk path tolerated the + /// same buffer and returned `Err(DiscRead)`. Behaviour on a caller error + /// must not depend on the drive's transfer limit, and a library inside a + /// long-running service must not panic on it at all. + /// + /// Every other read test stays on the single-chunk path, so this guard was + /// entirely unasserted and a mutation run flipped its arithmetic freely. + #[test] + fn an_undersized_buffer_errors_on_the_chunked_path_just_like_the_single_one() { + // max_transfer = 4 sectors, so a 10-sector read must chunk. + let mut h = chunking(4 * 2048, None); + let mut small = vec![0u8; 4096]; // 2 sectors' worth for a 10-sector read + + let chunked = h.drive.read(0, 10, &mut small, false); + assert!( + matches!(chunked, Err(Error::DiscRead { .. })), + "an undersized buffer on the chunked path must be an error, not a panic" + ); + + // The single-chunk path, same undersized buffer, same verdict. + let single = h.drive.read(0, 3, &mut small, false); + assert!( + matches!(single, Err(Error::DiscRead { .. })), + "the single-chunk path must agree" + ); + + // Exactly-sized still works, so the guard is not simply rejecting + // everything on the chunked path. + let mut exact = vec![0u8; 10 * 2048]; + assert!(h.drive.read(0, 10, &mut exact, false).is_ok()); + } + #[test] fn read_chunks_large_request_to_max_transfer() { // max_transfer = 4 sectors (4 * 2048 = 8192 bytes). A read of 10