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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3692 Β· 12 files
FileTypeDescriptionSize
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

VERDICT.md
↓ download raw

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 guest ata_raid_arrays is 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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched 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
↓ fix.diffper-fix-DF-3692

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() and STATUS yields two-level kernel memory disclosure - same index family.

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.