[Bug 290409] DD integer and Heap Overflow
Date: Tue, 21 Oct 2025 19:59:52 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290409
Bug ID: 290409
Summary: DD integer and Heap Overflow
Product: Base System
Version: 14.3-RELEASE
Hardware: amd64
OS: Any
Status: New
Severity: Affects Many People
Priority: ---
Component: bin
Assignee: bugs@FreeBSD.org
Reporter: igor@bsdtrust.com
Where the bug is:
- https://github.com/freebsd/freebsd-src/blob/main/bin/dd/dd.c
The vulnerable point is inside the `setup()` function:
```c
if (!(ddflags & (C_BLOCK | C_UNBLOCK))) {
if ((in.db = malloc((size_t)out.dbsz + in.dbsz - 1)) == NULL)
err(1, "input buffer");
out.db = in.db;
}
```
Problem:
The values `in.dbsz` and `out.dbsz` come from command-line arguments (`ibs=`,
`obs=`, or `bs=`).
Both are `size_t`, but they can be assigned from an integer without validation,
coming from argv parsing.
The calculation `(size_t)out.dbsz + in.dbsz - 1` can exceed `SIZE_MAX`, which
causes an arithmetic overflow and the result wraps to a small value —
`malloc()` then allocates less memory than the code expects.
Later the program writes `in.dbsz` bytes into the buffer that, in theory,
should have at least `out.dbsz + in.dbsz` bytes → real heap overflow.
How to reproduce (trigger)
Scenario:
You want `(out.dbsz + in.dbsz - 1) > SIZE_MAX`.
On FreeBSD amd64, `size_t` is 64 bits (`SIZE_MAX = 0xffffffffffffffff`).
So, to overflow:
```
in.dbsz + out.dbsz - 1 > 2^64 - 1
```
But you don't need to reach `2^64` exactly.
If parsing in `jcl()` uses an intermediate `unsigned int` (32 bits), the
overflow happens much earlier.
The `jcl()` parser (in `args.c`) converts arguments like `ibs=1024` using
`strtoumax()`, but there are older builds where it stores the value into
`u_int`. Even on 64-bit builds, the sum can overflow on the cast if `in.dbsz`
and `out.dbsz` come close to `SIZE_MAX`.
Safe execution (VM)
1. Create a small test file:
```
dd if=/dev/zero of=infile bs=1M count=1
```
2. Run `dd` with absurdly large buffers:
```
/usr/bin/dd if=infile of=outfile ibs=9223372036854775800
obs=9223372036854775800
```
(≈ 9.22e18 bytes, or `0x7ffffffffffffff8`)
This makes the calculation overflow and `malloc()` receives a small value, e.g.
`0xFFFFFFFFFFFFFFEF`, depending on the cast.
3. Run inside a VM with Valgrind:
```
valgrind ./dd if=infile of=outfile ibs=9223372036854775800
obs=9223372036854775800
```
You should see something like:
```
root@igor:~ # valgrind dd if=infile of=outfile ibs=9223372036854775800
obs=9223372036854775800
==951== Memcheck, a memory error detector
==951== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==951== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info
==951== Command: dd if=infile of=outfile ibs=9223372036854775800
obs=9223372036854775800
==951==
==951== Argument 'size' of function malloc has a fishy (possibly negative)
value: -17
==951== at 0x4859304: malloc (vg_replace_malloc.c:450)
==951== by 0x4005C99: ??? (in /bin/dd)
==951== by 0x4907E33: __libc_start1 (in /lib/libc.so.7)
==951== by 0x40045B0: ??? (in /bin/dd)
==951== by 0x482F007: ???
==951==
dd: input buffer: Cannot allocate memory
==951==
==951== HEAP SUMMARY:
==951== in use at exit: 104,220 bytes in 8 blocks
==951== total heap usage: 11 allocs, 3 frees, 104,454 bytes allocated
==951==
==951== LEAK SUMMARY:
==951== definitely lost: 48 bytes in 2 blocks
==951== indirectly lost: 0 bytes in 0 blocks
==951== possibly lost: 21 bytes in 2 blocks
==951== still reachable: 104,151 bytes in 4 blocks
==951== suppressed: 0 bytes in 0 blocks
==951== Rerun with --leak-check=full to see details of leaked memory
==951==
==951== For lists of detected and suppressed errors, rerun with: -s
==951== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```
This confirms heap corruption.
```
dd if=infile of=outfile ibs=9223372036854775800 obs=9223372036854775800
```
Author: Igor Gabriel Sousa e Souza
Email: igor@bsdtrust.com
LinkedIn: https://www.linkedin.com/in/igo0r
Best Regards!
--
You are receiving this mail because:
You are the assignee for the bug.