IOCATAREQUEST unclamped timeout: 1-tick completion-wait fallback completes and frees a channel-owned ata_request (UAF -> Fatal trap 12 in ata_start)
Summary
The 32-bit user timeout is copied verbatim; timeout=-1 makes the completion wait (timeout*hz*4) wrap to a ~1-tick expiry, so a request still queued behind others hits the 'taskqueue timeout' fallback which runs ata_completed directly in the ioctl thread (which never dequeues it), after which the issuer frees the request while the channel still owns it. REPRODUCED on guest: Fatal trap 12 at ata_start+0x1da after 107 direct-completion warnings, 12s into a 32-thread run on /dev/acd0. Taskqueue-pending completions also use the freed object and a lockuninit'ed done. Operator/root-gated (same class as DF-1643); slab-cached object + DMA-target-after-free corruption primitive. Fix validated: 19200 ioctls, 0 warnings, guest alive.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4475 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ata_timeout_uaf.c | poc | 32-thread IOCATAREQUEST timeout=-1 hammer | 4.0 KB | view raw |
| serial_a1_panic.log | panic | 107 WARNINGs + Fatal trap 12 capture | 22.3 KB | view raw |
| run.log | run-log | baseline summary + panic extract | 1.1 KB | view raw |
| fix.diff | suggested-fix | clamp + wait-dont-complete | 3.1 KB | view raw |
| fix_build.log | build-log | nativekernel rc=0 | 5.1 MB | β download |
| fix_run.log | run-log | patched: 19200 ioctls, 0 warnings | 177 B | view raw |
| env.txt | env | guest/kernel info | 314 B | view raw |
Fix verification
fixedCombined fix (clamp + wait-don't-complete) applied to guest /usr/src, nativekernel build rc=0, installed, rebooted. Identical PoC run: 19,200 ioctls completed, zero 'taskqueue timeout' messages, guest alive. Baseline panic signature gone.
fix_run.log (0 WARNING lines, 'poc: done, 19200 ioctls completed'); fix_build.log tail (build completed rc=0); fix_install.log
Confirmed kernel references
Detail
Exploit chain
operator/root opens /dev/acd0 -> 32 threads x IOCATAREQUEST(TEST_UNIT_READY, count=512, timeout=-1) -> backlog keeps requests queued past the 1-tick wait -> kernel completes them in the issuer thread and the issuer frees them while linked in ch->ata_queue -> ata_start dequeues a freed request (ata_start+0x1da cmpq (%rax),%r14 on a clobbered TAILQ next) -> Fatal trap 12, panic at db>. Escalation ceiling: heap grooming of the freed ata_request/buf (reallocated via concurrent IOCATAREQUESTs) turns this into kernel memory corruption; not pursued to uid0 in this run (privileged-gated device).
Evidence (decisive lines)
serial_a1_panic.log (full capture: WARNING spam + Fatal trap 12 + Stopped at ata_start+0x1da); fix_run.log (patched kernel: 19200 ioctls, 0 warnings, guest alive); fix.diff; fix_build.log/fix_install.log (nativekernel rc=0, installed, rebooted)
PoC changes
seed PoC was authored fresh for this finding (no prior pack); had to mirror struct ata_ioc_request exactly (64 bytes) and keep count!=0 to avoid DF-1644 overlap
Verified recommended fix
Clamp the ioctl timeout to >= ATA_DEFAULT_TIMEOUT and never complete a channel-owned request from the issuer thread (see fix.diff)
Verdict
IOCATAREQUEST passes the user timeout unclamped (ata-all.c:488); with timeout=-1 the completion wait degenerates to ~1 tick, the ata-queue.c:144-151 fallback directly completes a request that is still channel-owned, and the issuer then frees it (ata-all.c:514-515) -> UAF. Reproduced: 107 'taskqueue timeout' WARNINGs then Fatal trap 12 in ata_start+0x1da (fault 0x0) ~12s into a 32-thread run on /dev/acd0; guest died at db>. Operator/root-gated (device 0600 root:operator), same reachability class as DF-1643. Primitive is a slab-cached ata_request UAF plus a kmalloc'd buf still targeted by device DMA after free.
No comments yet.