afd_describe(): ksprintf of device-controlled u64 mediasize into char[16] β deterministic 1-byte NUL stack overflow at attach
Summary
Both capacity paths (READ_CAPACITY and READ_CAPACITY_16 with u64 wrap) can drive mediasize/1048576 to 14 digits - the render is 17 bytes into char[16], a fixed-value 1-byte NUL stack write corrupting an adjacent pointer local. Arithmetic proof in pack (smallest trigger 10^13 MiB). In-situ blocked by the same QEMU blocker. char[32]+ksnprintf fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4875 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.2 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| proof.c | β | 2.4 KB | view raw | |
| proof.txt | β | 896 B | view raw | |
| env.txt | β | 195 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 630 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-4875 β afd_describe() 1-byte stack NUL overflow via device-controlled mediasize
sys/dev/disk/nata/atapi-fd.c:446-451:
char sizestring[16];
if (fdp->mediasize > 1048576 * 5)
ksprintf(sizestring, "%juMB", fdp->mediasize / 1048576);
mediasize (u_int64_t) is fully device-controlled:
* READ_CAPACITY path (atapi-fd.c:384): (u64)be32toh(capacity) * blocksize
β up to (2^32-1)^2 β 1.84e19;
* READ_CAPACITY_16 path (atapi-fd.c:396): be64toh(capacity) * blocksize
computed in u64 (wraps mod 2^64) β any 64-bit value.
mediasize/1048576 can therefore have up to 14 decimal digits, and
"%juMB" renders 14 + 2 + NUL = 17 bytes into char sizestring[16] β
a 1-byte (NUL) stack buffer overflow at device attach time.
Proof
See proof.txt (generated by proof.c; pure arithmetic, host-side):
path1 READ_CAPACITY : cap=blocksize=0xFFFFFFFF -> '17592186036224MB' = 16 chars + NUL = 17 bytes -> OVERFLOW path2 READ_CAP_16 : cap=2^64-1,blocksize=1 -> '17592186044415MB' = 16 chars + NUL = 17 bytes -> OVERFLOW
The lowest triggering mediasize is 10^13 MiB β 1.05e19 (e.g. a device answering READ_CAPACITY_16 with capacity β₯ 2.44e16 sectors @512B, or READ_CAPACITY with maxed capacity+blocksize fields).
Status: untested (in situ) β proof is arithmetic
The write is deterministic the moment a (crafted, buggy or wedged) ATAPI floppy reports an oversized capacity; no runtime is needed to establish the 17 > 16 fact. On-guest execution is blocked because the audit guest cannot host an ATAPI direct-access device (QEMU 7.2: only ide-hd/ide-cd; see env.txt and the DF-4874 pack for the recorded blocker).
Impact ceiling
One NUL byte one past a 16-byte stack local in afd_describe(), at attach.
Depending on compiler frame layout it can land on an adjacent local pointer
(ch/atadev/fdp) and turn later device_printf derefs into a faulted
pointer β a device-triggered kernel memory-corruption primitive of very
limited range (fixed value 0x00, fixed position). Crafted-hardware class.
Fix
fix.diff (git-apply-able, checked): enlarge to char sizestring[32] and
switch to ksnprintf() with the buffer size so no device input can ever
overflow it.
DF-4875 VERDICT β afd_describe() 1-byte stack NUL overflow
Status: untested in situ (no ATAPI direct-access hardware on guest); mathematically certain
Root cause (path:line)
afd_describe()declareschar sizestring[16](sys/dev/disk/nata/atapi-fd.c:446) and formats the device-reported media size with an unboundedksprintf(sizestring, "%juMB", fdp->mediasize / 1048576)(atapi-fd.c:448-449) β reached whenevermediasize > 5 MiB.fdp->mediasizeis attacker(device)-controlled 64-bit:- READ_CAPACITY:
fdp->mediasize = (u_int64_t)be32toh(capacity.capacity) * fdp->sectorsize(atapi-fd.c:384) β product up to (2^32-1)^2 β 1.84e19; - READ_CAPACITY_16:
fdp->mediasize = be64toh(capacity_big.capacity) * fdp->sectorsize(atapi-fd.c:396) β u64 multiply, wraps to any value. - Max rendered length:
(2^64-1)>>20 = 17592186044415β 14 digits β"17592186044415MB"+ NUL = 17 bytes into a 16-byte stack array.
Why certain without a run
The proof is pure arithmetic on the exact types involved
(proof.txt, regenerable via proof.c): both capacity paths demonstrably
produce mediasize β₯ 1.049e19 from well-formed device responses, and
ksprintf/vsnprintf semantics write exactly strlen+1 bytes. There is no
code path that clamps mediasize before afd_describe() is called at attach
(atapi-fd.c:130). What a run could only add is the identity of the stack
slot clobbered by the 17th byte β that depends on compiler frame layout,
not on the bug.
In-situ blocker (documented, not a refutation)
afd_describe() runs only from afd_attach() (atapi-fd.c:130), which runs
only for ATAPI direct-access devices (atapi-fd.c:90-91). QEMU 7.2
provides no ATAPI direct-access emulation (-device help lists only
ide-hd, ide-cd), so no afd device can attach on the audit guest.
Recorded in the DF-4874 pack (run.log: "afd0 device_t is NULL").
Impact
Single NUL byte written one past a 16-byte stack local at driver attach β
a real kernel stack memory-corruption bug (CWE-787) with a hostile-device
precondition and very constrained primitive (value 0x00, fixed offset,
attach-time only). Worst realistic outcome: corrupted adjacent local
pointer (ch/atadev/fdp at atapi-fd.c:443-445) β faulted deref inside
device_printf (atapi-fd.c:455-458) β kernel panic; escalation to
controlled exploitation is not plausible with a fixed 0x00 byte.
Fix validation
fix_status: not_testable β the trigger device cannot exist on the audit
guest (see above); the fix (sizestring[32] + ksnprintf with size,
fix.diff) removes the overflow by construction (bounded formatting API,
buffer larger than the 17-byte worst case).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableNo afd hardware on guest for an A/B run; fix removes the overflow by construction (bounded formatter, 32-byte buffer > 17-byte worst case)
fix.diff (git apply --check OK)
Confirmed kernel references
Detail
Exploit chain
crafted/buggy ATAPI floppy answers READ_CAPACITY with capacity=blocksize=0xFFFFFFFF (or READ_CAPACITY_16 with huge capacity) -> mediasize >= 1.049e19 -> afd_attach -> afd_describe ksprintf writes 17 bytes into char[16] -> NUL lands on adjacent stack local (ch/atadev/fdp at atapi-fd.c:443-445) -> faulted deref in device_printf (attach-time panic); primitive limited to a fixed 0x00 byte
Evidence (decisive lines)
proof.txt (exact 17>16 for both paths, smallest trigger value); VERDICT.md; DF-4874/run.log for the shared hardware blocker
PoC changes
proof.c generated fresh; no seed existed
Verified recommended fix
char sizestring[32] + ksnprintf(sizestring, sizeof(sizestring), ...) for all three branches
Verdict
afd_describe() ksprintf's a device-controlled u64 media size into char sizestring[16]; mediasize from READ_CAPACITY (up to (2^32-1)^2) or READ_CAPACITY_16 (arbitrary u64, wraps) renders up to '17592186044415MB' = 17 bytes incl NUL β a deterministic 1-byte NUL stack overflow at attach. Proven by exact arithmetic (proof.txt) on both capacity paths; in-situ run blocked only by the absence of ATAPI direct-access hardware under QEMU 7.2.
No comments yet.