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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3867 Β· 5 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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 registered INTR_MPSAFE (emu10kx.c:3208) and iterates ihandler[] 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) holds sc->lock and 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_testable
baseline no→ patch + rebuild →patched clean

Not validated: no baseline repro possible (no hardware), skip-class finding.

sys/dev/sound/pci/emu10kx.c:993-999
per-fix-DF-3867

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.