amr_pci_detach's EBUSY guard is defeated: AMR_STATE_OPEN is cleared on ANY close (not last) and is TOCTOU-open β kldunload frees the softc while a /dev/amr0 descriptor still references it via dev->si_drv1 (UAF)
Summary
The detach guard trusts a single OPEN bit that amr_close clears on any close: with two descriptors, one close defeats the guard and kldunload frees the softc while the surviving fd's si_drv1 dangles - every management ioctl then resolves into freed memory. An open racing past the check has the same effect. Hardware+privileged-unload gated. Open-count + guarded-detach fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4707 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| trigger.sh | β | 703 B | view raw | |
| fix.diff | β | 955 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4707 β amr_pci_detach's EBUSY guard is defeated: AMR_STATE_OPEN is
cleared on ANY close (and is TOCTOU) β kldunload frees the softc
while a /dev/amr0 descriptor is still open
What
sys/dev/raid/amr/amr_pci.c:365-366 refuses detach only while
AMR_STATE_OPEN is set:
if (sc->amr_state & AMR_STATE_OPEN)
return(EBUSY);
But the flag is not a reference count:
amr_open()(amr.c:422-431) sets the single bit on every open;amr_close()(amr.c:438-447) clears the single bit on any close β not the last close.
With two descriptors open on /dev/amr0 (mode 0660, group
operator), closing one clears the flag while the other fd is
still live. kldunload amr (privileged) then passes the guard β
amr_pci_shutdown + amr_pci_free run: destroy_dev(), the softc /
DMA teardown, devclass release β while the surviving descriptor
still points at the cdev. Its next ioctl resolves the softc via
dev->si_drv1 (amr.c:707), which now dangles β use-after-free on
the freed softc (or NULL/stale devclass deref after the module is
fully unloaded).
Secondary aspect of the same guardlessness: amr_open() sets the bit
unconditionally, so an open that races past amr_pci_detach's check
(before amr_pci_free) sets the flag on a sc that is being torn down
β classic TOCTOU; there is no lock or freeze around the check.
Reachability / gating
- Requires LSI MegaRAID hardware (amr must be attached) β absent from
the audit guest (verified: no such PCI device,
/dev/amr*missing), so this is hw-gatedinconclusive. - The unload itself needs privilege; the victim fd can belong to an
unprivileged
operator-group user. Security value: robustness / admin-foot-gun + memory-safety fallout, hence Low.
Trigger (requires hardware)
exec 3<>/dev/amr0 # open #1 -> AMR_STATE_OPEN set exec 4<>/dev/amr0 # open #2 (same bit, no counting) exec 4>&- # ANY close -> flag cleared, fd 3 still open kldunload amr # guard passes; softc + cdev torn down dd if=/dev/amr0 of=/dev/null bs=1 count=0 # ioctl/read via stale si_drv1
Fix
Make the guard a real open-count (see fix.diff): counter in the
softc, incremented in amr_open, decremented in amr_close,
amr_pci_detach returns EBUSY while non-zero.
VERDICT β DF-4707
status: inconclusive (hardware-gated) Β· reproduced: 0 Β· analysis confidence: likely
Was it reproduced?
No β the audit guest has no MegaRAID controller and no /dev/amr*
(verified this run), so the guard sequence cannot be exercised.
kldload amr finds no device. The defeat is, however, certain from
inspection:
amr_open(amr.c:422-431):sc->amr_state |= AMR_STATE_OPEN;β bit set per open, no exclusivity, no counting.amr_close(amr.c:438-447):sc->amr_state &= ~AMR_STATE_OPEN;β bit cleared on any close, so with 2 open descriptors, 1 close makesamr_pci_detach's guard (amr_pci.c:365-366) pass while a descriptor is live.amr_pci_freethen runsdestroy_dev+ full DMA/softc teardown (amr_pci.c:371β477-527) while the survivor's cdev back-pointersi_drv1(set at amr.c:263, consumed at amr.c:707 in every management ioctl) still references the softc β use-after-free / stale-devclass NULL deref on the next ioctl.- TOCTOU: an
amr_openracing past the detach check sets the bit on a sc mid-teardown (no lock/freeze anywhere on this path).
Why Low
The unload requires privilege; the primary victim is an operator-group user's management fd. The bug is a refcount/lifetime error with memory-safety fallout, not a self-service privesc.
Fix validation
Not possible on this guest (no amr hardware). fix_status:
not_testable. The proposed counter is the standard DF driver pattern
(see e.g. other cdevsw drivers that track opens).
Guest state
Guest untouched; stayed up.
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 validation impossible on this guest (no amr hardware); counter-based guard authored from the line-accurate inspection
fix.diff (amrvar.h + amr.c open/close counting + amr_pci.c guard)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md inspection trace (amr_pci.c:365, amr.c:430, amr.c:446, amr.c:263, amr.c:707); trigger.sh (fd-3/fd-4 + kldunload sequence); guest gate evidence in ../DF-4705/guest_session.log
PoC changes
no seed; trigger.sh authored (requires hardware)
Verified recommended fix
track an open-reference count (amr_opencnt) in the softc and make amr_pci_detach EBUSY while any descriptor remains open (see fix.diff)
Verdict
amr_pci_detach's only guard is AMR_STATE_OPEN (amr_pci.c:365-366), but amr_close clears that single bit on ANY close, not the last (amr.c:446), and amr_open sets it per open without counting (amr.c:430) β with two descriptors on /dev/amr0, one close defeats the guard, so a privileged kldunload proceeds to amr_pci_free (destroy_dev, softc/DMA teardown, amr_pci.c:371,477-527) while the surviving fd's dev->si_drv1 (set amr.c:263, consumed amr.c:707) dangles -> use-after-free on the next management ioctl; an open racing past the detach check has the same effect (TOCTOU, no lock). Cannot be exercised on the audit guest: no MegaRAID hardware (no 0x101e/0x1000:1960 PCI device, /dev/amr* absent β verified).
No comments yet.