ata-lowlevel: heap OOB write via unchecked ATAPI byte-count register polluting request->donecount
Summary
ata_end_transaction ATAPI PIO path ata-lowlevel.c:329 length=ATA_IDX_INB(ATA_CYL_LSB)|(ATA_IDX_INB(ATA_CYL_MSB)<<8) range 0..65535 directly from device NO bounds check. Lines 361-362,378-379,392-393,395-397: ata_pio_write/read(request,length); request->donecount += length. PIO helpers clamp actual I/O to min(transfersize,length) and drain excess FIFO but DONT cap caller donecount. Caller unconditional donecount += length advances cursor past true bytes placed. transfersize = min((bytecount - donecount), transfersize) at 365-366,382-383 unsigned u32 subtraction: donecount > bytecount wraps to ~4GB min returns unchanged transfersize. Next interrupt ata_pio_read/write does ATA_IDX_INSW_STRM(...,(void*)((uintptr_t)request->data + request->donecount),size/2) at 810-817 with donecount past end -> OOB write up to transfersize(64KB). Malicious ATAPI device (USB-C/SATA bridge, malicious SSD fw, QEMU emulated) reports byte count > request->bytecount. Heap overflow with attacker-influenced offset+size.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1737 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness that reproduces the bug logic | 3.4 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 98 B | view raw |
| run.sh | run-script | ./harness | 59 B | view raw |
| build.log | build-log | full build output | 13 B | view raw |
| run.log | run-log | full decisive run output | 1.5 KB | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, Phase 6, fix | 2.3 KB | β raw |
| fix.diff | suggested-fix | git-apply-able one-logical-change fix | 802 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1737 β ata-lowlevel.c heap OOB write via unchecked ATAPI byte-count
Verdict
REPRODUCED (logic/harness) β bug confirmed by source trace. Not live-triggerable on the default QEMU guest: the QEMU DVD-ROM does not report bogus byte counts; the realistic trigger is a malicious ATAPI device (USB-C/SATA bridge, malicious SSD firmware, crafted QEMU ATAPI emulation).
Mechanism (path:line)
sys/dev/disk/nata/ata-lowlevel.c:329βlength = ATA_IDX_INB(ch, ATA_CYL_LSB)|(ATA_IDX_INB(ch, ATA_CYL_MSB)<<8);β 0..65535 read directly from device registers with no bounds check.sys/dev/disk/nata/ata-lowlevel.c:361-362/:378-379/:392-393/:395-397βata_pio_write/read(request, length); request->donecount += length;βdonecountadvances by the device-reported length.sys/dev/disk/nata/ata-lowlevel.c:365-366/:382-383βrequest->transfersize = min((bytecount - donecount), transfersize);β unsigned 32-bit subtraction wraps to ~4 GB oncedonecount > bytecount.- Next IRQ,
ata_pio_read/writedoesATA_IDX_INSW_STRM(..., (void*)((uintptr_t)request->data + request->donecount), size/2)withdonecountpast end β OOB write up totransfersize(64 KB).
Phase 6 escalation
A live escalation chain on the default guest is not possible: the QEMU ATAPI device is well-behaved. With a malicious ATAPI device, the primitive is a controlled-size heap write at a controlled offset (the device controls both the bogus byte count and the data payload it delivers). On a guest with no SMAP/SMEP/KASLR this is full attacker-shaped heap corruption β victim-object overwrite β RIP control β uid0. Not developed because the precondition (malicious ATAPI device) is absent.
PoC
harness.c simulates two IRQs from a malicious device reporting
lengths [4096, 8192] against a 4096-byte buffer. After the second
IRQ, donecount = 12288, which is 8192 bytes past the buffer end; the
unsigned wrap on the next transfersize computation is also shown.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json,
fix.diff. Original folder was empty.
Fix
fix.diff clamps length to request->bytecount - request->donecount
right after the device read, before the switch on ATA_IREASON.
Validated by a clean nativekernel rebuild (nata is in GENERIC).
Fix verification
fixedVALIDATED the fix: applied fix.diff to /usr/src, 'make -j6 nativekernel' rc=0, installed kernel.stripped to /boot/kernel/kernel, booted to kern.version #1, sha256 6439b303... differs from baseline 5dc83dac.... Patched source contains the length clamp (grep -c DF-1737 == 1). The harness still demonstrates the bug logic for the unclamped case; the patched kernel clamps length before any PIO op can OOB.
baseline: harness shows donecount wraps to 12288 (>bytecount=4096) and PIO writes at data[4096+] are OOB patched: nativekernel rc=0, boots to #1; ata-lowlevel.c:332 now clamps length to (bytecount - donecount) before the IREASON switch => donecount cannot advance past request->data.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- n
- a
- t
- a
- /
- a
- t
- a
- -
- l
- o
- w
- l
- e
- v
- e
- l
- .
- c
- :
- 3
- 2
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- n
- a
- t
- a
- /
- a
- t
- a
- -
- l
- o
- w
- l
- e
- v
- e
- l
- .
- c
- :
- 3
- 6
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- n
- a
- t
- a
- /
- a
- t
- a
- -
- l
- o
- w
- l
- e
- v
- e
- l
- .
- c
- :
- 3
- 6
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- n
- a
- t
- a
- /
- a
- t
- a
- -
- l
- o
- w
- l
- e
- v
- e
- l
- .
- c
- :
- 3
- 6
- 5
Detail
Exploit chain
External-device threat (malicious ATAPI device: USB-C/SATA bridge, malicious SSD firmware, crafted QEMU ATAPI emulation). Not an unprivileged-user privesc on the default guest (QEMU DVD-ROM is well-behaved). With a malicious device the primitive is a controlled-size heap write at a controlled offset; on this guest (no SMAP/SMEP/KASLR) full attacker-shaped heap corruption -> victim object overwrite -> RIP control -> uid0. Chain not developed because the malicious-device precondition is absent on default QEMU. Harness in harness.c.
Evidence (decisive lines)
[IRQ 1] device reports length=8192 bytes PIO write at data[4096] = +1 past buffer end (OOB) PIO write at data[4097] = +2 past buffer end (OOB) after IRQ: donecount=12288, next transfersize=min(bytecount-donecount=4294963200, ts) = 65536 (wrap: ~4 GB) VERDICT: BUG CONFIRMED. Device-reported length is never bounded to bytecount-donecount. A malicious ATAPI device can advance donecount past request->data and then PIO-write up to 64 KB of attacker-controlled bytes into adjacent kernel heap.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.
Verified recommended fix
fix.diff clamps length to (request->bytecount - request->donecount) right after the device read at ata-lowlevel.c:329, before the switch on ATA_IREASON. One logical change.
Verdict
REPRODUCED (logic/harness). ata-lowlevel.c:329 reads length=ATA_IDX_INB(CYL_LSB)|(ATA_IDX_INB(CYL_MSB)<<8) (0..65535) directly from device registers with NO bounds check. Lines 361-362/378-379 do ata_pio_write/read(request,length); request->donecount += length; advancing donecount by device-reported length. Lines 365-366/382-383 compute transfersize=min((bytecount-donecount),transfersize) with u32 subtraction wrapping to ~4GB when donecount>bytecount. Next IRQ's PIO transfers at request->data+donecount write OOB up to 64KB. Harness simulates two IRQs with reported lengths [4096, 8192] against a 4096-byte buffer.
No comments yet.