Missing cdb_len validation in DISK I/O path allows OOB read on short CDB pointer
Summary
ahci_xpt_scsi_disk_io() computes CDB pointer from csio->cdb_io.cdb_ptr when CAM_CDB_POINTER set and accesses CDB structures up to 16 bytes wide (cdb->read_capacity_16 cdb->ata_pass_12 cdb->ata_pass_16 cdb->rw_10/12/16) without ever consulting csio->cdb_len. ATAPI sibling path correctly bounds this with cdb_len<6||cdb_len>16 check but DISK path does not. User-supplied CCB via /dev/pass with CAM_CDB_POINTER and cdb_len<16 causes kernel to read past user-supplied CDB buffer. SMAP faults kernel panic. With kernel-address cdb_ptr (requires KASLR bypass) 14 bytes kernel memory read into FIS fields. [VERIFIED pass2: reproduced on live QEMU AHCI - cdb_len 6 CDB at page end with PROT_NONE neighbor Fatal trap user address access at ahci_xpt_scsi_disk_io+0x8f0 reading cdb+7; operator-group pass reach; ATAPI-style check fix-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2422 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 344 B | β raw | |
| VERDICT.md | β | 1.3 KB | β raw | |
| build.sh | β | 89 B | view raw | |
| env.txt | β | 254 B | view raw | |
| fix.diff | β | 528 B | view raw | |
| manifest.json | β | 723 B | view raw | |
| panic.txt | β | 494 B | view raw | |
| pass2422.c | β | 2.2 KB | view raw | |
| run.log | β | 406 B | view raw | |
| run.sh | β | 148 B | view raw | |
| verdict.json | β | 1.9 KB | view raw |
DF-2422 -- evidence pack
Verdict: reproduced (reproduced=1, impact=panic, confidence=certain)
Build: cc -O -o pass2422 pass2422.c -lcam
Run: ./pass2422 -c (control: completes) ; ./pass2422 (trigger: panic)
See VERDICT.md for the full narrative, run.log/panic.txt for the decisive output, fix.diff for the post-verification fix.
DF-2422 VERDICT
Status: reproduced Impact: panic Confidence: certain
ahci_xpt_scsi_disk_io never consults csio->cdb_len: a pass(4) XPT_SCSI_IO CCB with CAM_CDB_POINTER and cdb_len=6 whose CDB buffer ends at a page boundary (next page PROT_NONE) made the kernel read cdb->rw_10 bytes 6..9 past the user buffer: Fatal user address access from kernel mode, fault VA = cdb_page+1, stopped at ahci_xpt_scsi_disk_io+0x8f0 movzwl 0x7(%r13),%ecx - exactly the 16-bit block-count field at cdb+7. The control run (identical CDB, pointer mid-page) completed normally, proving the DISK path is reached and the fault is the OOB read.
Kernel references
Guest
DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Notes
the ATAPI sibling path already validates cdb_len (ahci_cam.c:1512); the fix mirrors it
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_testableConfirmed kernel references
Detail
Exploit chain
operator-group /dev/passN + a crafted CCB -> kernel reads up to 10 CDB bytes regardless of cdb_len -> with an unmapped neighbor page: fatal kernel-mode fault (local DoS); with kernel-address cdb_ptr (KASLR bypass): 14 bytes of kernel memory read into FIS fields
Evidence (decisive lines)
run.log (control output), panic.txt (fault VA and stopped-at instruction)
PoC changes
live QEMU AHCI hardware (ahci0.0 DISK, /dev/pass0); trigger via libcam CAM_CDB_POINTER CCB; no rig needed
Verified recommended fix
mirror the ATAPI check in ahci_xpt_scsi_disk_io: reject CCBs with csio->cdb_len < 6 || > 16 before dereferencing the CDB
Verdict
ahci_xpt_scsi_disk_io never consults csio->cdb_len: a pass(4) XPT_SCSI_IO CCB with CAM_CDB_POINTER and cdb_len=6 whose CDB buffer ends at a page boundary (next page PROT_NONE) made the kernel read cdb->rw_10 bytes 6..9 past the user buffer: Fatal user address access from kernel mode, fault VA = cdb_page+1, stopped at ahci_xpt_scsi_disk_io+0x8f0 movzwl 0x7(%r13),%ecx - exactly the 16-bit block-count field at cdb+7. The control run (identical CDB, pointer mid-page) completed normally, proving the DISK path is reached and the fault is the OOB read.
No comments yet.