hdspe_intr leaks sc->lock (lockmgr LK_EXCLUSIVE) on device_get_children error path
- File:
sys/dev/sound/pci/hdspe.c - Lines: 96β101 (lock + bare return), 113 (only unlock)
- Severity: Info
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U:C:N/I:N/A:L - CWE: CWE-667 Improper Locking (missing unlock on error path)
- Confidence: speculative
- Status: new
Summary
hdspe_intr() acquires sc->lock (a lockmgr LK_EXCLUSIVE sleepable mutex
via snd_mtxlock, sound.h:359) at hdspe.c:96, then on the
device_get_children failure branch at hdspe.c:100-101 does a bare
return; without calling snd_mtxunlock.
The lock is therefore held permanently. Because sc->lock guards every later
channel/mixer operation (snd_mtxlock in hdspe-pcm.c trigger/getptr/
setblocksize and hdspemixer paths), any subsequent audio ioctl blocks
forever in unkillable sleep, permanently wedging the device and any process
touching it β a local DoS.
The bug is a real missing-unlock code defect; it is currently unreachable
because device_get_children (sys/kern/subr_bus.c:1594-1617) allocates with
M_INTWAIT and cannot fail (always returns 0), so the branch is dead today.
Reported as defense-in-depth: it becomes a live, reliable DoS the moment that allocator or the helper ever gains a failure mode.
Root cause
hdspe_intr():
snd_mtxlock(sc->lock); /* hdspe.c:96 */
status = hdspe_read_1(sc, HDSPE_STATUS_REG); /* :98 */
if (status & HDSPE_AUDIO_IRQ_PENDING) {
if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
return; /* :101 -- LEAK */
...
}
snd_mtxunlock(sc->lock); /* :113 -- bypassed */
The return; at :101 bypasses the matching snd_mtxunlock(sc->lock); at
hdspe.c:113.
snd_mtxlock is #define snd_mtxlock(m) lockmgr(m, LK_EXCLUSIVE)
(sys/dev/sound/pcm/sound.h:359), so the held lock is a sleepable exclusive
lockmgr lock; the next lockmgr(LK_EXCLUSIVE) on sc->lock will block
indefinitely.
Reachability gate
device_get_children at sys/kern/subr_bus.c:1605 does
kmalloc(..., M_TEMP, M_INTWAIT | M_ZERO) and unconditionally
return(0) at subr_bus.c:1616, so err is always 0 and the branch is not
taken on a stock kernel.
Threat model
Local DoS of the snd_hdspe audio device and any process using /dev/dspN or
/dev/mixerN bound to it.
Requires the driver to be loaded/attached (RME HDSPe AIO/RayDAT PCIe hardware,
or a faked PCI device) and requires device_get_children to return non-zero.
On the current DragonFly kernel that return cannot happen (M_INTWAIT), so
there is no demonstrated impact today; the finding is the latent
missing-unlock that would turn into a permanent kernel-side hang the instant
the allocation can fail.
No privilege gain, no info leak.
Proof of concept
Not currently reproducible on stock DragonFly because the trigger condition
(device_get_children != 0) is unreachable with the present M_INTWAIT
allocator (sys/kern/subr_bus.c:1605,1616).
To demonstrate the defect statically / prove the latent impact:
- Confirm the missing unlock β
grep -n 'snd_mtxunlock' sys/dev/sound/pci/hdspe.cshows the only unlock is athdspe.c:113, after the early return at:101. - If a reviewer wants a live reproduction in a test build, temporarily force
the error path (e.g. patch the condition, or build a kernel where
device_get_childrenis made to fail once), open/dev/dsp0for playback, trigger an interrupt (start a stream), and observe that the second channel operation (e.g.SNDCTL_DSP_SPEEDviahdspechan_setspeed, orhdspechan_setblocksize) hangs inlwkt_gettoken/lockmgrforever βprocstat -kk <pid>shows the thread stuck inlockmgr + snd_mtxlock.
Because the path is dead on stock kernels, no working PoC that panics or hangs an unmodified guest can be produced; this is correctly classified Info/speculative.
Recommended fix
Release the lock before returning on the error branch.
--- a/sys/dev/sound/pci/hdspe.c
+++ b/sys/dev/sound/pci/hdspe.c
@@ -97,8 +97,11 @@ hdspe_intr(void *p)
status = hdspe_read_1(sc, HDSPE_STATUS_REG);
if (status & HDSPE_AUDIO_IRQ_PENDING) {
if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
- return;
+ goto done;
for (i = 0; i < devcount; i++) {
scp = device_get_ivars(devlist[i]);
if (scp->ih != NULL)
scp->ih(scp);
}
hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0);
kfree(devlist, M_TEMP);
}
+done:
snd_mtxunlock(sc->lock);
}
Equivalently, the minimal one-line fix is to replace the bare return; at
hdspe.c:101 with { snd_mtxunlock(sc->lock); return; } (note devlist is
unallocated on this path, so no kfree(devlist) is needed).
The goto done form above is preferred for clarity.
Unrelated dead-store (out of scope)
While here, a maintainer should also fix the unrelated dead-store in
hdspe_init (hdspe.c:247 vs :251) where HDSPM_CLOCK_MODE_MASTER is
overwritten by hdspe_encode_latency(7).
References
sys/dev/sound/pci/hdspe.c:96,100-101,113β lock + leak + bypassed unlocksys/dev/sound/pcm/sound.h:359βsnd_mtxlock=lockmgr(LK_EXCLUSIVE)sys/kern/subr_bus.c:1605,1616βdevice_get_childrenusesM_INTWAIT, always returns 0
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2011 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.6 KB | β raw |
| fix.diff | suggested-fix | Replace bare 'return' with 'goto done' where done: snd_mtxunlock(sc->lock); | 604 B | view raw |
| ../fix_build_new.log | build-log | Batch kernel build with new fixes (rc=0, -Werror) | 5.6 MB | β download |
DF-2011 β PoC Verification Verdict
Category: sound (IN GENERIC)
Source: sys/dev/sound/pci/hdspe.c:96-113
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-25
Verdict: REPRODUCED (source-only confirmation; GENERIC-compiled, no HW)
Mechanism
hdspe_intr: snd_mtxlock(sc->lock); then if (device_get_children(...) != 0) return; β returns without snd_mtxunlock(sc->lock). Leaked exclusive lock; subsequent interrupt or any lock-taker deadlocks.
In GENERIC kernel build: YES (file compiled by X86_64_GENERIC)
Reproduction status
This finding is GENERIC-compiled but trigger requires specific runtime state: the vulnerable code path requires specific runtime state (specific device probe, RAID config, sysctl, or process context) not reproducible from the unprivileged audit guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not exercised. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Replace bare 'return' with 'goto done' where done: snd_mtxunlock(sc->lock);
See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): hdspe_intr: snd_mtxlock(sc->lock); then if (device_get_children(...) != 0) return; - returns without snd_mtxunlock(sc->lock). Leaked exclusive lock; subsequent interrupt or a
Verified recommended fix
REPRODUCED (source-only): hdspe_intr: snd_mtxlock(sc->lock); then if (device_get_children(...) != 0) return; - returns without snd_mtxunlock(sc->lock). Leaked exclusive lock; subsequent interrupt or any lock-taker deadlocks.
Verdict
REPRODUCED (source-only): hdspe_intr: snd_mtxlock(sc->lock); then if (device_get_children(...) != 0) return; - returns without snd_mtxunlock(sc->lock). Leaked exclusive lock; subsequent interrupt or any lock-taker deadlocks.
No comments yet.