idad_detach frees the idad softc under open descriptors and in-flight bios β DF-5013/DF-5025 family detach UAF
Summary
No open-count gate and no in-flight drain; cdev teardown is async so open fds keep si_drv1 into the freed softc - strategy writes freed devstat/ida_submit memory, late completions write freed stats. Root-only (kldunload; no runtime delete path unlike mfi DF-5013). Hardware-gated. Opencount-gate fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5046 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| env.txt | β | 1.0 KB | view raw | |
| fix.diff | β | 1.0 KB | view raw | |
| verdict.json | β | 3.8 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-5046 β idad_detach frees the idad softc under open descriptors and in-flight bios (LD-family detach UAF)
File: sys/dev/raid/ida/ida_disk.c (pass 2, GLM 5.3)
Bucket: memcorrupt | Severity: Low | Pattern: DF-5013 (mfi_disk) / DF-5025 (ips_disk)
What the bug is
idad_detach() (sys/dev/raid/ida/ida_disk.c:302-311) releases the softc's
registrations with no gate on open file descriptors and no drain of in-flight
I/O:
static int
idad_detach(device_t dev)
{
struct idad_softc *drv;
drv = (struct idad_softc *)device_get_softc(dev);
devstat_remove_entry(&drv->stats);
disk_destroy(&drv->disk);
return (0);
}
After it returns, the bus/devclass layer frees the idad_softc. Nothing in
the teardown path protects the two consumers of dsk->si_drv1:
-
Open fds (DF-5025 leg).
disk_destroy()sends a synchronous DISK_DISK_DESTROY (sys/kern/subr_disk.c:876-880); the handler waits only for disk-enumeration refsdp->d_refs(subr_disk.c:540-542) and then destroys the cdevs (subr_disk.c:546-548).destroy_dev()->devfs_destroy_dev()is asynchronous (sys/vfs/devfs/devfs_core.c:759-763) and only drops sysrefs; the cdev object survives while any open fd holds a reference, and neither that path noridad_detachever clearssi_drv1or revectors it away fromid_ops. A read/write on the still-open fd then runsidad_strategy()->idad_getsoftc()(ida_disk.c:94-99) which returns the dangling softc pointer; the very next statements write through it (devstat_start_transaction(&drv->stats)ida_disk.c:174,ida_submit_buf(drv->controller, bio)ida_disk.c:175 β queue-head writes into freed memory). -
In-flight bios (DF-5013 leg).
dev_dstrategy()takes abio_track_ref()ondev->si_track_*(sys/kern/kern_device.c:375-380) β this keeps the cdev alive, not the softc.bio_track_wait()exists (sys/kern/vfs_bio.c:565) but is called only from vnode-drain paths (sys/kern/vfs_subr.c:326,377,526,799); no cdev-destroy path drains it. A bio completing after detach runsidad_intr()(ida_disk.c:213-226) ->devstat_end_transaction_buf(&drv->stats, bp)on the freed softc.
Threat model / trigger
idad_detach is reachable only via the device detach path (module unload of
the in-kernel ida/idad drivers β kldstat -v on the standard X86_64_GENERIC
guest shows ida/idad, pci/ida compiled in). Requires root (kldunload) β
hence Low, same rating rationale as DF-5025. (DF-5013 was High only because
mfi had an unprivileged-time AEN offline path that deleted disks at runtime;
ida has no runtime delete path and no ioctls at all.)
PoC / verification status
Hardware-gated: the ida driver attaches only to Compaq SMART/IDA PCI boards
(ida_pci.c:146-170 board_id[] = subdevice ids 0x40300E11..0x40580E11,
vendor 0x0E11, matched at ida_pci.c:199-210). The audit QEMU guest has no
Compaq PCI device (pciconf -l | grep -i 0e11 empty β see env.txt), so no
/dev/idad* node exists and the trigger cannot be exercised. The code trace
above is line-accurate and reproduces the exact DF-5025 family defect.
Fix
Gate detach on an open count (and let the controller side quiesce per DF-1915's fix). See fix.diff.
Reproduce (on hardware)
# on a machine with a Compaq SMART/IDA controller: dd if=/dev/idad0 of=/dev/null bs=64k count=100 & # keep fd open + I/O in flight kldunload ida_pci (or detach path via hw.pci....) # as root, while the above runs # observe: idad_strategy / idad_intr touch freed softc (KASAN/MEMGUARD or panic)
DF-5046 VERDICT β idad detach UAF under open fds / in-flight bios
status: untested (hardware-gated) | impact: none demonstrated (UAF-write by code trace) | confidence: likely
Reproduced?
No β and it cannot be reproduced on this audit guest: the trigger requires a physical Compaq SMART/IDA (SMART-2/P, SMART-2SL, SA3200/3100ES/221, Integrated Array, RAID LC2, SA4200/4250ES/431) PCI array controller. Evidence of gating (env.txt, captured live from the guest):
pciconf -lcontains no vendor0x0e11(Compaq) function ->ida_pci_match()(sys/dev/raid/ida/ida_pci.c:193-210) can never match,ida_pci_attachnever runs, noidadchildren are created, and/dev/idad*does not exist (ls /dev/idad*-> NO_IDAD_DEVS).- The driver code itself is present in the guest kernel build
(
kldstat -vlistsida/idadandpci/ida), i.e. this is dormant code one PCI match away from live on any machine with the card β not dead code.
Why the finding is real (line-accurate trace)
idad_attachsetsdsk->si_drv1 = drv(ida_disk.c:280) wheredrvis the driver softc owned by the devclass, freed shortly afteridad_detachreturns (ida_disk.c:302-311).idad_detachhas no open-count gate, no in-flight drain, and never clearssi_drv1. Compare the modern DFly pattern (e.g. nvme_disk, mmcsd) which refuses EBUSY while the disk is open.disk_destroy(sys/kern/subr_disk.c:876-880) -> DISK_DISK_DESTROY handler (subr_disk.c:527-556): waits fordp->d_refs(disk enumeration refs only), thendestroy_dev(dp->d_cdev)+destroy_only_dev(dp->d_rawdev).destroy_dev->devfs_destroy_devis asynchronous (sys/vfs/devfs/devfs_core.c:759-763); the worker (devfs_core.c:1440-1459) unlinks nodes andrelease_dev()s the sysref. The cdev object survives while any fd holds it open, and itssi_opsstaysid_ops,si_drv1stays the freed softc.- Post-detach I/O through a still-open fd: physio ->
dev_dstrategy(sys/kern/kern_device.c:355-389) ->idad_strategy(ida_disk.c:145-189): -idad_getsoftc(dev)(ida_disk.c:94-99) returns danglingsi_drv1; - readsdrv->flags(ida_disk.c:161); -devstat_start_transaction(&drv->stats)(ida_disk.c:174) β write into freed softc; -ida_submit_buf(drv->controller, bio)(ida_disk.c:175) β dereferences freeddrv->controllerand inserts the bio into freed queue heads (sys/dev/raid/ida/ida.c:365-370). - In-flight bios are equally unguarded:
bio_track_refindev_dstrategypins the cdev, nothing waits ondev->si_track_*in any cdev destroy path (bio_track_waitis only used by vnode drain, sys/kern/vfs_subr.c:326, 377, 526, 799), and a late completion runsidad_intr(ida_disk.c:213-226) ->devstat_end_transaction_buf(&drv->stats, bp)β another freed-softc write.
Exploit chain (assessment, not exercised)
Local root is already the precondition (kldunload), so this is not an
escalation primitive β it is a root-triggerable UAF-write (kernel memory
corruption class). With heap grooming around the freed idad_softc/
ida_softc (both large, controllable size), the devstat/queue-head writes
could be steered into attacker-planted objects; on this intentionally
unhardened guest that would be a viable corruption primitive, but it buys an
attacker nothing beyond the root they already have. Real-world impact:
crash/UAF on detach, integrity of the write path if the softc is reallocated.
Fix assessment
fix.diff adds the open-count gate (idad_open/idad_close increment/
decrement, idad_detach returns EBUSY while open) β the same remedy as the
DF-5025/DF-5013 family fixes. It intentionally does not attempt to fix the
controller-side in-flight problem (that is DF-1915's fix: bus_generic_detach +
drain in ida_detach). fix_status=not_testable: no IDA hardware exists on the
guest to build/boot a validated fix kernel against; the diff is line-accurate
against sys/dev/raid/ida/ida_disk.c + idavar.h and compiles by inspection
(trivial counter + EBUSY).
Bottom line
Genuine DF-5025-family UAF, code-certain, untestable here solely because the guest has no Compaq SMART/IDA hardware (proven in env.txt). Low severity: root-only trigger, no unprivileged path (unlike DF-5013's AEN path).
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_testablefix.diff adds opencount to struct idad_softc, increments/decrements in idad_open/idad_close, and returns EBUSY from idad_detach while any fd is open β closes the open-fd UAF leg; the in-flight leg additionally needs DF-1915's controller quiesce. Cannot be build/boot validated: no IDA hardware on the guest. Diff is git-apply-clean against sys/.
fix.diff (git apply --check: APPLIES_CLEAN); context hunks at ida_disk.c idad_open/idad_close/idad_detach and idavar.h struct idad_softc
Confirmed kernel references
- sys/dev/raid/ida/ida_disk.c:302-311
- sys/dev/raid/ida/ida_disk.c:94-99
- sys/dev/raid/ida/ida_disk.c:145-189
- sys/dev/raid/ida/ida_disk.c:213-226
- sys/kern/subr_disk.c:876-880
- sys/kern/subr_disk.c:527-556
- sys/vfs/devfs/devfs_core.c:759-763
- sys/vfs/devfs/devfs_core.c:1440-1459
- sys/kern/kern_device.c:355-389
- sys/kern/vfs_bio.c:565
Detail
Exploit chain
root (kldunload ida) -> idad_detach frees softc under open fd -> open fd read/write -> idad_strategy -> idad_getsoftc returns dangling si_drv1 -> devstat/queue writes into freed idad_softc+ida_softc. Not an escalation primitive (root already held); heap grooming around the freed softc would give kernel memory corruption on hardware.
Evidence (decisive lines)
env.txt (live guest: no 0x0e11 PCI function, NO_IDAD_DEVS, driver present in kldstat); VERDICT.md full trace with path:line for every hop; fix.diff (git apply --check clean)
PoC changes
No runnable PoC exists (hardware-gated). Pack contains the hardware-absence evidence and the verified-code trace instead; fix.diff generated against sys/ and validated with git apply --check.
Verified recommended fix
Gate idad_detach on an open counter maintained in idad_open/idad_close (return EBUSY while open), and drain in-flight bios before freeing (controller side per DF-1915).
Verdict
Genuine DF-5025/DF-5013-family detach UAF: idad_detach (ida_disk.c:302-311) frees the softc with no open-count gate and no in-flight drain; disk_destroy's sync wait covers only disk-enumeration refs (subr_disk.c:540-548), cdev teardown is async and keeps the cdev alive under open fds with si_ops=id_ops and si_drv1 dangling, and no cdev path drains si_track (bio_track_wait is vnode-only). Post-detach read/write via a still-open fd or a late idad_intr writes into freed memory (devstat_start_transaction ida_disk.c:174, ida_submit_buf ida_disk.c:175, devstat_end_transaction_buf ida_disk.c:224). Untested solely because the guest has no Compaq SMART/IDA hardware (pciconf: no 0x0e11 device, no /dev/idad*) β the code trace is line-accurate and the driver is compiled into X86_64_GENERIC. Severity Low: trigger requires root (kldunload); unlike mfi there is no unprivileged runtime delete path.
No comments yet.