β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4876

afd_sense(): three uninitialized stack structs consumed as ATAPI response buffers β€” short (but successful) device responses publish stale kernel stack as disk geometry

Summary

The response structs are never zeroed and ata_atapicmd treats short data-in underruns as success; only page_code is validated while geometry fields are consumed directly - stale stack becomes mediasize/geometry, weakly leaked via disk-info ioctls and used in LBA math (the !mediasize guard keeps the DF-1945 div0 closed). Hostile-device model. bzero-before-each-command fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4876 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.0 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
env.txt β€” 195 B view raw
fix.diff β€” 738 B view raw
manifest.json β€” 582 B view raw
verdict.json β€” 2.5 KB view raw

DF-4876 β€” afd_sense() consumes uninitialized stack on short ATAPI responses

sys/dev/disk/nata/atapi-fd.c:341-417: afd_sense() declares three uninitialized stack structs and passes them to ata_atapicmd():

struct afd_capacity capacity;        /* :345 */
struct afd_capacity_big capacity_big;/* :346 */
struct afd_capabilities capabilities;/* :347 */
...
if (!ata_atapicmd(dev, ccb1, (caddr_t)&capacity, sizeof(...), ATA_R_READ, 30)) {
    fdp->sectorsize = be32toh(capacity.blocksize);
    fdp->mediasize  = (u_int64_t)be32toh(capacity.capacity) * fdp->sectorsize; ...
...
    if (!ata_atapicmd(dev, ccb3, (caddr_t)&capabilities, 40, ATA_R_READ, 30) &&
        capabilities.page_code == ATAPI_REWRITEABLE_CAP_PAGE) {
        fdp->heads      = capabilities.heads;          /* struct offset 12 */
        fdp->sectors    = capabilities.sectors;        /* offset 13 */
        fdp->sectorsize = be16toh(capabilities.sector_size);  /* 14-15 */
        fdp->mediasize  = be16toh(capabilities.cylinders) *   /* 16-17 */
                          fdp->heads * fdp->sectors * fdp->sectorsize;

ata_atapicmd() (sys/dev/disk/nata/ata-queue.c:188-214) issues the packet command and returns request->result. ATAPI data-in phases routinely underrun (device returns fewer bytes than the allocation length) while still completing with success status β€” donecount < bytecount is not an error in the nata completion path. The kernel never zeroes request->data.

Therefore a device that returns, e.g., an 8-byte MODE SENSE(10) response (header only) or a truncated capabilities page leaves capabilities.heads/sectors/sector_size/cylinders (offsets 12..17) as stale kernel stack bytes, which are then:

  1. multiplied into fdp->mediasize (u32 wrap already tracked as DF-2508) and published as disk geometry via disk_setdiskinfo() (atapi-fd.c:217) β€” a (weak) kernel-stack info leak observable through the disk-info ioctls, and
  2. used as divisors/geometry in afd_open() (atapi-fd.c:211-215) and afd_strategy()'s LBA math (atapi-fd.c:258-259), producing garbage I/O placement; a zero divisor here is pre-conditioned by the !mediasize guard, but arbitrary non-zero garbage is not.

Only capabilities.page_code (offset 8) is validated (:405) β€” the consumed fields sit past it.

Status: untested (in situ) β€” requires an ATAPI direct-access device

Same blocker as DF-4874/4875: the audit guest cannot attach an afd device (QEMU 7.2 has no ATAPI direct-access emulation), so a short-response device cannot be presented. The defect is established by trace: ata_atapicmd() never zeroes the buffer, short-but-successful ATAPI data-in is not an error (ata-queue.c:188-214 + nata interrupt completion), and the struct fields past the validated page_code are consumed directly (atapi-fd.c:406-410).

Fix

fix.diff: bzero() all three response structs before each command, so a short device response yields zeros (which the existing !mediasize / ENXIO paths then reject cleanly) instead of stale stack.

VERDICT.md
↓ download raw

DF-4876 VERDICT β€” afd_sense() uninitialized-stack consumption

Status: untested in situ (hardware absent); established by line trace

Root cause

afd_sense() uses three uninitialized stack structs as ATAPI response buffers (sys/dev/disk/nata/atapi-fd.c:345-347). ata_atapicmd() (sys/dev/disk/nata/ata-queue.c:188-214) sets request->data = data, request->bytecount = count, queues, and returns request->result; the nata completion path treats a successful command with a short data-in transfer (donecount < bytecount) as success β€” no zero-fill of the untransferred tail exists anywhere on this path (verified in ata-queue.c and the request setup; bzero of request->data is absent).

The only field validated before use is capabilities.page_code (atapi-fd.c:405). A device returning a short MODE SENSE(10) page (or a short READ_CAPACITY / READ_CAPACITY_16 block descriptor) leaves offsets 12-17 of struct afd_capabilities (heads, sectors, sector_size, cylinders β€” sys/dev/disk/nata/atapi-fd.h:62-65) or the tail of the capacity structs as stale kernel stack, which the driver then publishes as disk geometry (atapi-fd.c:406-410, 383-384, 395-396).

Consequences

  • Kernel stack bytes are multiplied into fdp->mediasize / d_ncylinders/d_secpertrack and exposed to userland through the disk-info ioctls after open (atapi-fd.c:207-217) β€” a degraded (product-of-fields) stack-content leak, CWE-457.
  • Garbage geometry feeds afd_strategy()'s bio_offset / fdp->sectorsize LBA math (atapi-fd.c:258) β€” misplaced raw I/O on a device whose actual geometry differs.
  • Zero-valued garbage is caught by the existing !mediasize β†’ ENXIO guard (atapi-fd.c:204), so this does not reopen the known DF-1945 div0 sink.

Why untested, honestly

Triggering requires an ATAPI direct-access device that returns a short data-in response β€” the audit guest cannot attach ANY afd device (QEMU 7.2 -device help has no ATAPI floppy; recorded in DF-4874/run.log: "afd0 device_t is NULL"). This is a hardware-availability blocker, not a refutation: every hop of the trace (uninitialized declaration β†’ non- zero-filling transport β†’ unvalidated consumption) was verified in source.

Fix validation

fix_status: not_testable β€” same hardware blocker. The fix (bzero of the three structs before the commands, fix.diff) converts any short response into zeros, which the driver already rejects via !mediasize ENXIO.

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_testable
baseline no→ patch + rebuild →patched clean

No afd hardware on guest; fix converts short responses into zeros which the existing !mediasize ENXIO path rejects

fix.diff (git apply --check OK)
↓ fix.diffper-fix-DF-4876

Confirmed kernel references

Detail

Exploit chain

crafted ATAPI floppy returns short MODE SENSE(10)/READ_CAPACITY data with success status -> struct tail stays stale stack (atapi-fd.c:345-347, consumed at :383-384/:395-396/:406-410) -> garbage geometry via disk_setdiskinfo (atapi-fd.c:207-217) -> observable via DIOCG*-style disk info (weak kernel-stack leak) + misplaced raw I/O from afd_strategy LBA math

Evidence (decisive lines)

VERDICT.md (full trace incl. ata_atapicmd non-zeroing path); README.md; DF-4874/run.log (shared hardware blocker)

PoC changes

n/a β€” source-trace finding; hardware blocker shared with DF-4874

Verified recommended fix

bzero(&capacity)/(&capacity_big)/(&capabilities) before each ata_atapicmd in afd_sense()

Verdict

afd_sense() hands three uninitialized stack structs to ata_atapicmd(); short-but-successful ATAPI data-in responses (normal underrun, not an error in the nata completion path, no zero-fill of request->data) leave heads/sectors/sector_size/cylinders past the only validated field (page_code) as stale kernel stack, which is then published as disk geometry β€” CWE-457 with a degraded stack-content leak via disk-info ioctls and garbage LBA math in afd_strategy. Verified hop-by-hop in source; not runnable because the guest cannot attach an ATAPI direct-access device.