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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4707 Β· 5 files
FileTypeDescriptionSize
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-gated inconclusive.
  • 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.md
↓ download raw

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 makes amr_pci_detach's guard (amr_pci.c:365-366) pass while a descriptor is live.
  • amr_pci_free then runs destroy_dev + full DMA/softc teardown (amr_pci.c:371β†’477-527) while the survivor's cdev back-pointer si_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_open racing 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_testable
baseline no→ patch + rebuild →patched clean

fix 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)
↓ fix.diffper-fix-DF-4707

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).