emu_intr() NULL-check and indirect call of ihandler[].irq_func are unsynchronized against emu_intr_unregister() -> call through NULL function pointer (panic)
Summary
emu_intr() is registered INTR_MPSAFE and iterates ihandler[] with no lock: it tests irq_func != NULL and then loads irq_func again for the indirect call. emu_intr_unregister() NULLs the fields under sc->lock - a lock the hardirq does not take. A record-channel STOP (unprivileged /dev/dsp user) landing between the two loads makes the kernel call address 0: guaranteed panic. DoS only, probabilistic window (needs simultaneous playback for interrupt flow). Snapshot-once fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3867 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 2.6 KB | view raw | |
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 1.5 KB | β raw | |
| verdict.json | β | 2.2 KB | view raw | |
| manifest.json | β | 530 B | view raw |
DF-3867 β emu_intr() NULL-check/call race on ihandler[].irq_func
File: sys/dev/sound/pci/emu10kx.c:993-999 (racy consumer),
emu10kx.c:948-978 (unregistering side).
Verification was not attempted at runtime (contract skip for Low / speculative findings) and is additionally blocked by the same hardware gate recorded in the DF-3866 pack: this QEMU guest has no Creative Labs PCI device, so the driver never attaches and no /dev/dsp exists.
Build / run (on real hardware)
cc -O2 -o trigger trigger.c
./trigger /dev/dsp0
Expected on vulnerable hardware: kernel panic (call through NULL
irq_func) within minutes-to-hours of racing; on patched kernel: run
completes with "no panic occurred".
Defect summary
emu_intr() is registered INTR_MPSAFE (emu10kx.c:3208) and takes no
lock while iterating ihandler[]. emu_intr_unregister() (under
sc->lock) NULLs .irq_func/.softc at emu10kx.c:966-967. The
check-then-call pair at emu10kx.c:994-997 loads irq_func twice with no
atomicity β a rec-channel STOP (unprivileged, via /dev/dsp) racing the
card's own IRQ delivery can call a just-NULLed function pointer β
guaranteed panic. Not memory-corruption exploitable (control-flow goes to
address 0); DoS only.
DF-3867 VERDICT
Status: untested (contract skip: Low/speculative; hardware gate identical to DF-3866 β
guest has no Creative Labs PCI device, snd_emu10kx loads but never attaches).
Source-level race
emu_intr()(emu10kx.c:980-1033) is registeredINTR_MPSAFE(emu10kx.c:3208) and iteratesihandler[]without any lock:- :995
((void *)sc->ihandler[i].irq_func) != NULLβ load #1 - :996
ack |= sc->ihandler[i].irq_func(...)β load #2 + indirect call emu_intr_unregister()(:948-978) holdssc->lockand NULLs.irq_func/.softc(:966-967) β the lock does not exclude the hardirq.
A record-channel STOP (unprivileged /dev/dsp user, emu10kx-pcm.c:992/1162)
landing between load #1 and load #2 β call through NULL β guaranteed panic.
Impact ceiling: kernel DoS (panic). Not exploitable control flow.
Fix: read irq_func/softc once into locals (compiler barrier / lock
snapshot) before testing and calling.
Not run at runtime; trigger.c documents the exact trigger shape for
reproduction on real hardware.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableNot validated: no baseline repro possible (no hardware), skip-class finding.
sys/dev/sound/pci/emu10kx.c:993-999
Confirmed kernel references
Detail
Evidence (decisive lines)
trigger.c (exact trigger shape: concurrent playback for interrupt flow + rec START/STOP loop); DF-3866 pack run.log for the hardware gate; sys/dev/sound/pci/emu10kx.c:993-999 vs :964-967
PoC changes
n/a (not executed)
Verified recommended fix
Snapshot irq_func/softc into locals once under sc->lock (or a single atomic load) in emu_intr() before the NULL test and call
Verdict
Verification skipped per contract for Low/speculative findings, and doubly gated: the QEMU guest has no Creative Labs PCI device (proven live in the DF-3866 pack: kldload snd_emu10kx attaches nothing), and even on real hardware the race window between the unlocked NULL check of ihandler[i].irq_func (emu10kx.c:995) and the indirect call (emu10kx.c:996) is a few instructions wide against the lock-taking unregistering thread (emu10kx.c:964-967), making userland-triggered reproduction probabilistic. Source-level the race is real: emu_intr() runs INTR_MPSAFE and lockless while emu_intr_unregister() NULLs .irq_func/.softc under sc->lock; worst case is a call through NULL -> panic (DoS), not exploitable control flow.
No comments yet.