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

Unconditional per-ioctl failure diagnostics + full chain rebuild per SETFMT/SPEED/CHANNELS give an unprivileged user an unbounded kernel console print rate; a one-shot guest-wide starvation/livelock was observed during such storms (inconclusive)

Summary

14 unconditional device_printf on failed rebuilds plus destroy-rebuild per format ioctl = unbounded kernel console print rate from an unpriv /dev/dsp holder. During pass-2 stress the 6-vCPU guest suffered a minutes-long whole-system starvation (sshd timeouts, continuous console spam) - NOT reproduced in two controlled re-runs, root-cause stack unobtainable (DDB reads input from the file-backed serial), and the QEMU rig lacks host audio (possible confounder). Filed speculative/inconclusive per honesty rules. ~1300 adversarial ioctls otherwise all held (no memory-safety defect in the chain builder). Gate-diagnostics-behind-snd_verbose hardening fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4602 Β· 12 files
FileTypeDescriptionSize
feeder_fuzz.c β€” 4.1 KB view raw
wedge-serial-full.log β€” 24.0 KB view raw
rerun1.log β€” 237 B view raw
rerun2.log β€” 954 B view raw
README.md β€” 4.1 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
build.sh β€” 119 B view raw
run.sh β€” 176 B view raw
env.txt β€” 608 B view raw
fix.diff β€” 4.0 KB view raw
verdict.json β€” 3.9 KB view raw
manifest.json β€” 1.2 KB view raw

DF-4602 β€” guest-wide starvation/livelock during unprivileged dsp format-change storms (INCONCLUSIVE)

File under audit: sys/dev/sound/pcm/feeder_chain.c Observed event class: local DoS (kernel console spam + pcm lock churn starved a 6-vCPU guest for minutes) Verdict: inconclusive β€” observed ONCE, NOT reproduced in 2 controlled re-runs; root-cause stack unobtainable on this rig; QEMU-without-audio-backend is a plausible confounder.

The event (original session, 2026-09-22 ~02:00-02:12, guest booted via vm_snd.sh)

Sequence: stage 1 = 3 unprivileged fuzzers on /dev/dsp0,/dev/dsp1 (30 s, no wedge); stage 2 = 5 unprivileged fuzzers (incl. vchan clones /dev/dsp0.N after dev.pcm.0.play.vchans=4) + concurrent root churn of dev.pcm.0.play.vchanrate / vchanformat / bitperfect (no wedge during); stage 3 = 3 O_RDWR fuzzers incl. the vchan node /dev/dsp0.0 (no immediate wedge). One to three minutes AFTER all activity, the guest entered a minutes-long starvation state:

  • sshd unusable ("Connection timed out during banner exchange") for minutes at a time, intermittently recovering;
  • qemu host process pegged at 94-107% (β‰ˆ1+ cores) with oscillating guest load;
  • serial console (wedge-serial-full.log) shows continuing pcm0/pcm1: feeder_build_format(): can't add feeder_format, feeder_build_rate(): can't set source rate, feed_format_init(): failed ... bursts β€” i.e. leftover fuzzer processes kept cycling failing feeder-chain rebuilds against pcm channels whose interrupt source is dead (QEMU audio backend absent);
  • no panic, no console output beyond the spam (no watchdog, no deadlock detector fired);
  • the guest eventually self-recovered partially, then degraded again; a Ctrl-Alt-Esc DDB break from the QEMU monitor (Debugger("manual escape to debugger") in the serial log) stopped all CPUs, but DDB reads input from the serial console which is file-backed on this rig β€” no ps/bta was obtainable β€” so the spinner's kernel stack was never captured.

Re-runs (controlled, after vm.sh reset with-src, fresh boot, vm_snd.sh)

  • rerun1 (minimal): single unprivileged fuzzer, /dev/dsp1, 45 s β€” NO wedge; cpu 10-20%, ssh ok at every 10 s poll (rerun1.log).
  • rerun2 (full original recipe): 5 fuzzers + identical root churn β€” cpu 96-111% for ~2.5 min as expected, ssh ok except ONE transient banner-exchange timeout at t+155 s, then healthy for the remaining ~4 min; load 0.00 at the end; fuzzers exited cleanly (rerun2.log).

Why filed at all

The one-shot event was severe (whole-guest starvation from unprivileged dsp activity + root parent-format churn) and the in-file surface is real: feeder_chain.c emits unconditional device_printf() diagnostics on every failed rebuild (feeder_build_format :153/:163, feeder_build_rate :226/:236, feeder_build_matrix :304/:314, feeder_build_volume :359/:370, feeder_build_eq :427/:437, feeder_build_root :470/:477, feeder_build_mixer :508/:518, plus feeder_chain :615/:626/:638/:645/:660/ :670), and every SNDCTL_DSP_SETFMT/SPEED/CHANNELS on a failing format destroys and rebuilds the whole chain (:575-579). An unprivileged user can sustain an unbounded rate of kernel console prints through this path (console-spam DoS surface, CWE-400). Whether that surface alone explains the guest-wide starvation β€” or whether the driver/dead-interrupt environment was the real driver β€” could not be established.

Reproduce

# host$ dfbsd-qemu/vm.sh down && dfbsd-qemu/vm_snd.sh
# host$ dfbsd-qemu/vm.sh run_root 'kldload snd_es137x; kldload snd_hda'
build.sh   # guest, as maxx: cc -O2 -o /tmp/feeder_fuzz /tmp/feeder_fuzz.c
run.sh     # guest, as maxx: the stage-2 recipe (see file)
# watch: host qemu CPU%, ssh health, boot-snd.log spam

fix.diff β€” gate the high-frequency per-ioctl failure diagnostics behind snd_verbose > 0 (keeps first-occurrence visibility via the existing bootverbose/snd_verbose conventions). Clearly labeled hardening: it removes the unbounded console-spam surface but is NOT claimed to fix the one-shot livelock (root cause undetermined).

VERDICT.md
↓ download raw

DF-4602 VERDICT β€” inconclusive

What happened

During the feeder_chain.c pass-2 stress campaign (see README.md for the exact recipe), the guest entered a minutes-long starvation state ~1-3 minutes after the fuzz activity: sshd refused banner exchanges for minutes at a time (intermittently recovering), the host qemu process burned 94-107% of a core with oscillating guest load (0.96 -> 1.83 -> decay -> repeat), and the serial console kept emitting pcm0/pcm1: feeder_build_format(): can't add feeder_format / feeder_build_rate(): can't set source rate / feed_format_init(): failed ... bursts as leftover fuzzer processes cycled failing feeder-chain rebuilds. No panic, no watchdog, no deadlock detector fired. The guest never fully died (TCP stack alive throughout) but was administratively unusable for minutes.

What I could not establish

  1. Root cause stack. A QEMU-monitor sendkey ctrl-alt-esc broke into DDB (Debugger("manual escape to debugger"), all CPUs stopped β€” visible in wedge-serial-full.log), but DragonFly's console is comconsole and the rig's serial is file-backed, so DDB never saw my PS/2-injected keystrokes. No ps/bta -> spinner stack unknown. Candidates: console-lock contention from the print storm, CHN_LOCK/PCM_BUSY churn, or a driver-level softint livelock.
  2. Determinism. After vm.sh reset with-src: * rerun1 (minimal: 1 fuzzer, /dev/dsp1, 45 s): NO wedge, cpu 10-20%. * rerun2 (full original recipe: 5 fuzzers + identical root churn): cpu 96-111% for ~2.5 min as expected, ONE transient ssh banner timeout, then healthy for ~4 more minutes; load 0.00 at the end.
  3. Real-hardware relevance. The QEMU rig has NO host audio backend (ALSA init fails on the host), so the guest audio devices never raise transfer interrupts β€” exactly the dead-interrupt environment in which chn_sleep-timeout / CHN_F_DEAD / rebuild-storm interactions concentrate. This confounder cannot be excluded and plausibly does not exist on real hardware.

Why filed (and at what confidence)

The in-file, indisputable component: feeder_chain.c emits unconditional device_printf() diagnostics on every failed rebuild and every format ioctl rebuilds the whole chain, so an unprivileged local user can sustain an unbounded kernel console print rate through SNDCTL_DSP_SETFMT / SPEED / CHANNELS storms (CWE-400 console-spam surface, lines :153, :164, :227, :237, :305, :315, :361, :371, :429, :439, :472, :479, :509, :519 and the destroy-rebuild loop at :575-579). Whether that surface alone explains the observed whole-guest starvation is undetermined. Filed Low/speculative, status inconclusive, impact dos (observed once).

Fix validation

Not possible: no reproducible baseline. fix.diff gates all 14 diagnostics behind snd_verbose > 0 (compile-safe; snd_verbose is extern in sound.h:314); it removes the spam surface and is the only defensible in-file hardening, but is NOT claimed to fix the livelock.

Guest state

Returned clean: vm.sh reset with-src after evidence capture.

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

No reproducible baseline exists (one-shot event), so a single-fix kernel validation cannot be performed; the diff is a compile-safe hardening generated from the pristine file (applies by construction).

fix.diff (182-line unified diff gating all 14 unconditional device_printf diagnostics in feeder_build_format/rate/matrix/volume/eq/root/mixer behind snd_verbose > 0; snd_verbose is extern in sound.h:314)
↓ fix.diffper-fix-DF-4602

Confirmed kernel references

Detail

Evidence (decisive lines)

wedge-serial-full.log (original session serial console: continuous pcm0/pcm1 feeder_build_* failure spam up to the 'Debugger(manual escape to debugger)' DDB break); rerun1.log (minimal trigger, no wedge); rerun2.log (full recipe, no wedge, load 0.00 at end); env.txt (QEMU has no host audio backend - ALSA init fails on host, so guest audio interrupts never fire)

PoC changes

n/a (fuzzer authored fresh for this batch; no seed PoC existed)

Verified recommended fix

Gate feeder_chain.c's per-ioctl failure diagnostics behind snd_verbose (fix.diff); if the livelock recurs on real hardware, capture ps/bta via a serial-input console or NMI panic and re-file with a determined root cause.

Verdict

A severe guest-wide starvation event (sshd banner-exchange timeouts for minutes, 94-107% qemu CPU with oscillating load, continuing feeder_build_format/feeder_build_rate console spam) occurred ONCE, 1-3 minutes after unprivileged dsp format-change fuzzing (incl. vchan clones) combined with root churn of dev.pcm.0.play.{vchanrate,vchanformat}/bitperfect on a QEMU guest whose audio devices have no host backend (interrupts never fire). Two controlled re-runs after vm.sh reset with-src (minimal single-fuzzer; full original recipe) did NOT reproduce it - only one transient ssh timeout in ~7 minutes of full-recipe load. Root-cause stack could not be captured: DDB accepts input only on the serial console, which is file-backed on this rig (a Ctrl-Alt-Esc monitor break stopped all CPUs but no ps/bta was possible). Filed because (a) the event was real and whole-guest in scope, and (b) the in-file surface is indisputable: feeder_chain.c emits unconditional device_printf diagnostics on every failed rebuild (:153,:164,:227,:237,:305,:315,:361,:371,:429,:439,:472,:479,:509,:519) and every SETFMT/SPEED/CHANNELS ioctl destroys and rebuilds the chain (:575-579), so an unprivileged user can sustain an unbounded kernel console print rate (CWE-400 console-spam surface). Whether that surface alone (vs the dead-interrupt QEMU audio environment or driver-level lock churn) explains the starvation is undetermined.