RAID ioctls index ata_raid_arrays[lun] with an unvalidated int: OOB pointer-table read -> wild ar_softc (panic; DELETE = kfree of wild pointer, STATUS = kernel-memory copyout)
Summary
IOCATARAIDSTATUS/DELETE/ADDSPARE/REBUILD pass the user-supplied int lun directly into the 16-entry global ata_raid_arrays[] with no <0/>=MAX_ARRAYS check. IOCATARAIDREBUILD with lun=0x8000000 faulted exactly at the table read (Stopped at ata_raid_ioctl+0x154: movq ata_raid_arrays(,%rax,8),%rcx). With a slot that holds mapped data, STATUS copies wild-object fields to userspace (kernleak + wild device derefs), REBUILD writes through the wild pointer, DELETE runs disk_destroy/devstat_remove_entry/kfree on it - arbitrary free. Root/operator-gated (/dev/ata 0600), hence Medium; distinct from DF-2495 which covers only the total_disks loops. FIX VALIDATED: 'rebuild ioctl: Invalid argument', guest healthy.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3692 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| f3_ioctl_lun.c | β | 1.8 KB | view raw | |
| build.sh | β | 99 B | view raw | |
| run.sh | β | 136 B | view raw | |
| run.log | β | 749 B | view raw | |
| panic.txt | β | 22.0 KB | view raw | |
| run.patched.log | β | 460 B | view raw | |
| fix.diff | β | 3.8 KB | view raw | |
| env.txt | β | 505 B | view raw | |
| verdict.json | β | 2.6 KB | view raw | |
| manifest.json | β | 3.3 KB | view raw |
DF-3692 β RAID ioctls index ata_raid_arrays[lun] with an unvalidated int
Root cause (source)
The RAID ioctl dispatch (sys/dev/disk/nata/ata-raid.c:241-270, chained from
ata_ioctl in ata-all.c:443-444) passes the user-supplied int lun
straight into ata_raid_arrays[MAX_ARRAYS] (16 pointers, kernel .bss):
1008: if (!(rdp = ata_raid_arrays[status->lun])) /* IOCATARAIDSTATUS */
1287: if (!(rdp = ata_raid_arrays[array])) /* IOCATARAIDDELETE */
1323: if (!(rdp = ata_raid_arrays[config->lun])) /* IOCATARAIDADDSPARE */
1373: if (!(rdp = ata_raid_arrays[array])) /* IOCATARAIDREBUILD */
No < 0 || >= MAX_ARRAYS check anywhere. A wild struct ar_softc * is then
dereferenced; depending on the subcommand:
- STATUS: fields of the wild object are copied back to userspace (kernleak) and
device_get_unit(rdp->disks[i].dev) walks wild device pointers;
- REBUILD: rdp->status |= AR_S_REBUILDING / rdp->rebuild_lba = 0 β writes
through the wild pointer;
- DELETE: disk_destroy(), devstat_remove_entry(), kfree(rdp, M_AR) on
the wild pointer β arbitrary free.
DF-2495 (known) covers the total_disks loop overflows in CREATE/STATUS;
the unbounded lun index into the global pointer table is a distinct,
previously unreported defect.
Reproduce
cc -O -o f3_ioctl_lun f3_ioctl_lun.c ./f3_ioctl_lun status 0 # baseline: "Device not configured" (ENXIO) ./f3_ioctl_lun rebuild 134217728 # lun = 0x8000000 -> +1 GiB OOB
Expected (baseline, unpatched)
Fatal trap 12: page fault while in kernel mode fault virtual address = 0xffffffffc114dec0 fault code = supervisor read data, page not present Stopped at ata_raid_ioctl+0x154: movq ata_raid_arrays(,%rax,8),%rcx
(fault VA == &ata_raid_arrays[0x8000000]; ssh dies, guest wedged in DDB.)
With fix.diff applied the same call returns EINVAL, no fault.
Reachability
/dev/ata is root-owned (0600 root:operator on this guest), so the ioctl family is privileged (root, or operator where devfs is permissive β the same privilege class DF-2495 already documented for this device). Impact is kernel memory corruption from a sanctioned management interface (incl. jail scenarios where /dev/ata is exposed).
Files
f3_ioctl_lun.c, panic.txt, run.patched.log, fix.diff, verdict.json
DF-3692 VERDICT
Status: reproduced (panic in the ioctl dispatcher; arbitrary-index OOB read of a kernel pointer table).
Reproduction
Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (ataraid compiled in, so
ata_raid_ioctl_func is live without kldload). /dev/ata is 0600
root:operator β run as root.
/tmp/poc/f3_ioctl_lun status 0 -> "status ioctl: Device not configured" (ENXIO, in-bounds) /tmp/poc/f3_ioctl_lun rebuild 134217728 -> (no reply; ssh dies)
Serial console:
Fatal trap 12: page fault while in kernel mode fault virtual address = 0xffffffffc114dec0 fault code = supervisor read data, page not present current process = 5424 Stopped at ata_raid_ioctl+0x154: movq ata_raid_arrays(,%rax,8),%rcx
0xffffffffc114dec0 - 8*0x8000000 = 0xffffffff8114dec0 = &ata_raid_arrays[0]
β the fault is the OOB table read itself (the dispatch was inlined into
ata_raid_ioctl). Guest wedged in DDB.
Primitive characterization
- Demonstrated: controlled-index OOB read of kernel .bss β fatal fault (panic / persistent DoS of the management path).
- Same primitive with adjacent (mapped) indexes yields a content-derived
struct ar_softc *that STATUS copies fields out of to userspace (two-level kernel-memory disclosure), REBUILD writes through (status |= 0x4,rebuild_lba = 0), and DELETE kfrees β arbitrary free. These variants are source-certain but were not individually exercised (they depend on what pointer value happens to sit at the chosen slot; on this no-KASLR guestata_raid_arraysis at 0xffffffff8114dec0 and slots 16+ are other kernel data). Root-gated (see README), hence Medium.
Fix validation
fix.diff adds < 0 || >= MAX_ARRAYS guards for the lun/array index in all
four subcommands (STATUS/DELETE/ADDSPARE/REBUILD). Patched kernel:
rebuild 134217728 β rebuild ioctl: Invalid argument, guest stays up
(run.patched.log). fix_status = fixed.
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
fixedPatched kernel: same ioctl returns 'Invalid argument' (EINVAL), guest stays up; in-bounds status 0 still ENXIO. Panic gone.
run.patched.log: 'rebuild ioctl: Invalid argument' + uptime continues
Confirmed kernel references
Detail
Exploit chain
root opens /dev/ata -> IOCATARAIDREBUILD lun=0x8000000 -> ata_raid_ioctl dispatch -> movq ata_raid_arrays(,%rax,8) OOB read -> unmapped kernel VA -> fatal trap 12, DDB wedge. With lun chosen so the slot holds a controlled/known pointer, DELETE yields kfree(
Evidence (decisive lines)
run.log (baseline ENXIO in-bounds vs OOB panic; Stopped-at line names the exact faulting instruction), panic.txt full trap frame
PoC changes
ioctl numbers/_IOWR sizes taken from sys/sys/nata.h (IOCATARAIDREBUILD _IOW('a',204,int)); none of the seed-typical errors
Verified recommended fix
bounds-check lun/array (0..MAX_ARRAYS-1) in all four subcommands (fix.diff hunks 1,2,4,5)
Verdict
The four RAID ioctls pass an unvalidated int lun into ata_raid_arrays[16] (kernel .bss). IOCATARAIDREBUILD with lun=0x8000000 faulted exactly at the OOB table read (fault VA = &ata_raid_arrays[0]+8*lun), panic in ata_raid_ioctl+0x154, guest wedged. Same index drives STATUS (wild-object fields copied to userspace - kernleak; wild device derefs), REBUILD (writes through wild pointer) and DELETE (disk_destroy/devstat_remove_entry/kfree on wild pointer - arbitrary free); those variants are source-certain but not individually exercised. Root-gated (0600 root:operator /dev/ata) so Medium despite the kfree primitive.
No comments yet.