Skip to content

dd: count=N iflag=count_bytes underflows when a conversion writes more than N bytes (debug panic, count limit bypassed in release) #13434

Description

@relative23

Summary

calc_loop_bsize derives the remaining byte budget for count=N iflag=count_bytes from the bytes written so far (wstat.bytes_total), while below_count_limit gates the copy loop on bytes read. Any conversion that writes more than it reads — conv=block record expansion, conv=sync padding of partial reads — pushes the written total past N while the read total is still below it, and the u128 subtraction in

https://github.com/uutils/coreutils/blob/058a2a656ea3ac0d5eee5b6a8f6e5e10be817bd8/src/uu/dd/src/dd.rs#L1436

underflows.

Debug builds: panic

Deterministic reproducer (any input whose conv=block output exceeds the byte count works):

$ yes aaaaaaaaaa | head -c 2000 > input.txt
$ cargo run -p uu_dd -- if=input.txt of=out.bin conv=block cbs=1024 count=1000 iflag=count_bytes

thread 'main' panicked at src/uu/dd/src/dd.rs:1436:33:
attempt to subtract with overflow

The first loop iteration reads 512 bytes and block-expands them to ~47 KiB of output; on the second iteration 1000 − 47_104 underflows.

Release builds: the count limit is silently bypassed

With overflow checks off the subtraction wraps, min(ideal_bsize, huge) returns the full block size again, and every following iteration reads a full-sized block — count no longer limits anything. Observable with the reproducer above (release build reads 1512 input bytes instead of 1000; GNU reads exactly 1000), or via strace with conv=sync on a pipe with partial reads, where the read sizes go 512 → 488 → 488 → back to 512 once the wrap happens.

Notes

  • GNU dd for the reproducer above outputs 93,184 bytes (91 records from exactly 1000 input bytes).
  • After fixing the budget computation, uutils outputs 94,208 bytes (92 records): uutils converts each copy-loop chunk independently, so a record spanning the 512-byte chunk boundary is padded early. That divergence is pre-existing and independent of count (same input without any count=: uutils 189,440 vs GNU 186,368 bytes) and out of scope here.
  • Found while working on dd: align the I/O buffer to the page size for O_DIRECT #13373; the affected line dates back to the original 2022 implementation.

I have a fix ready (derive the budget from rstat.bytes_total, matching below_count_limit, with a saturating subtraction) and will open a PR right after filing this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions