aac_ioctl_send_raw_srb sleeps on channel NULL while its CMFREE callback wakes channel &cm β permanent unkillable process when the FIB pool is exhausted
Summary
aac.c:3094 does lksleep(cm, ...) with cm==NULL; the CMFREE event callback stores the command through arg==&cm and calls wakeup(arg) i.e. wakeup(&cm). The channels never match, so when aac_alloc_command() fails the ioctl thread sleeps without PCATCH forever - D-state, unkillable. The sibling aac_ioctl_sendfib() gets it right with lksleep(&cm,...). One-token typo. Any operator-group user (dev is 0640 root:operator, aac_open has no priv check) on an AAC-equipped host hangs an unkillable process per attempt once all FIBs are outstanding - local DoS; no memory corruption.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3819 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| aac_rawsrb_hang.c | β | 2.6 KB | view raw | |
| build.sh | β | 104 B | view raw | |
| run.sh | β | 109 B | view raw | |
| build.log | β | 193 B | view raw | |
| run.log | β | 396 B | view raw | |
| run.2.log | β | 396 B | view raw | |
| env.txt | β | 323 B | view raw | |
| fix.diff | β | 396 B | view raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| verdict.json | β | 2.3 KB | view raw |
VERDICT β DF-3819
Status: not_reproduced (hardware-gated; source-certain defect)
Root cause
sys/dev/raid/aac/aac.c:aac_ioctl_send_raw_srb():
3076: cm = NULL;
...
3082: if (aac_alloc_command(sc, &cm)) {
3083: event = kmalloc(sizeof(struct aac_event), M_AACBUF, M_NOWAIT|M_ZERO);
...
3090: event->ev_type = AAC_EVENT_CMFREE;
3091: event->ev_callback = aac_ioctl_event;
3092: event->ev_arg = &cm; /* waker will wakeup(&cm) */
3093: aac_add_event(sc, event);
3094: lksleep(cm, &sc->aac_io_lock, 0, "aacraw", 0); /* BUG: cm == NULL */
The completion callback aac_ioctl_event() (aac.c:2959-2971) runs from
aac_release_command() (aac.c:1417-1420) when a FIB is freed, stores the
new command through arg and calls wakeup(arg) where
arg == &cm β the address of the kernel stack slot. The sleeper,
however, sleeps on the channel whose value is cm, i.e. NULL at that
point (it is only assigned by the callback after the wakeup that never
reaches it).
The sibling function aac_ioctl_sendfib() gets it right:
3003: lksleep(&cm, &sc->aac_io_lock, 0, "sendfib", 0);
lksleep() without PCATCH is uninterruptible, and no other code path in
the driver does wakeup(NULL), so the mismatched sleeper never wakes:
the calling thread hangs permanently in state D and cannot be killed.
Trigger / reachability
- Device node
/dev/aacd0(aliasesafa%d,hpn%d) is created0640 root:operatorat attach (aac.c:317-320) β no privilege check exists inaac_open()(aac.c:2780-2791), so any member of groupoperatorcan reach the ioctl. - The hang requires exactly one precondition:
aac_alloc_command()returnsEBUSYonce (aac.c:1370-1377) β i.e. all FIBs outstanding, trivially arranged on real hardware by parallel management traffic.
Why not reproduced on the guest
The QEMU audit guest has no Adaptec AAC PCI HBA:
ls /dev/aac* /dev/asr* /dev/afa* /dev/hpn*βNo such file or directory(env.txt, run.log)pciconf -lshows zero devices with aac vendor IDs (0x9005/0x9004 Adaptec, 0x1028 Dell PERC, 0x1044 DPT) β env.txt- the aac driver is compiled into
X86_64_GENERIC(kldload aacβ "module aac/aacd already exists"), but attach never runs without hardware, so the control device never exists and the ioctl path cannot be entered at all.
The defect itself is deterministic and visible in the source: a one-token
typo (lksleep(cm vs lksleep(&cm), proven by direct comparison with the
correct sibling two hundred lines above.
Fix
fix.diff: sleep on &cm. (Upstream FreeBSD's ancestor of this code uses
tsleep(&cm, ...) β the DragonFly port mangled the &.)
Fix validation
Not possible on this guest (no hardware to attach the driver to);
fix_status: not_testable.
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 cannot be exercised: baseline path requires an attached AAC controller, absent from the QEMU guest (no /dev/aacd0, no matching PCI vendor IDs). fix.diff is the one-token correction mirroring aac.c:3003.
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (source excerpt + guest-absence proof); run.log (open ENOENT as root and as user); env.txt (uname, no aac PCI devices, driver compiled into kernel); fix.diff (one-token fix)
PoC changes
Trigger written fresh (no seed): native FSACTL_SEND_RAW_SRB = _IO('8',19) computed from aac_ioctl.h, arg passed as pointer-to-pointer per aac.c:2832 'arg = (caddr_t)arg'.
Verified recommended fix
Sleep on the address of the local variable: lksleep(&cm, ...) in aac_ioctl_send_raw_srb (aac.c:3094).
Verdict
aac_ioctl_send_raw_srb() sleeps on chan NULL (lksleep(cm,...) with cm==NULL at aac.c:3094) while its CMFREE callback wakes chan &cm (aac.c:2970); the correct sibling aac_ioctl_sendfib() uses lksleep(&cm,...) at aac.c:3003. Once the FIB pool is exhausted the ioctl thread sleeps uninterruptibly forever - unkillable D-state process reachable by group operator through /dev/aacd0. Hardware-gated: the audit guest has no AAC HBA (no /dev/aacd0, zero matching PCI vendors), so the path cannot be entered; defect is certain from source comparison.
No comments yet.