⬢ DragonFlyBSD Kernel Audit
DF-4952 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4952",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Reproduced at feeder level on the audit guest against real, unmodified sys/dev/sound/pcm/feeder_mixer.c + feeder_matrix.c + the real generated snd_fxdiv_gen.h (md5-verified vs the tree): feed_mixer_rec (:194-204) has no getsize/free clamp (unlike the PLAY caller chn_wrfeed channel.c:425-427), so when the feeder directly below the REC mixer over-returns (in-tree generator FXROUND misround, proven class DF-4846/DF-4847: FXROUND(16390,12)=16392), sndbuf_acquire(count+delta) with free==bufsize inflates b->rl past b->bufsize (buffer.c:615-635 KASSERT-only, compiled out on the production guest), sndbuf_getfree() wraps to ~4G, and the loops min(cnt, maxfeed) passes maxfeed (~65544) requests into b->tmpbuf, an allocation of only round_page(bufsize) (~20KB): the real feed_matrix code then writes 65544 bytes from the tmpbuf base - rear-guard smash measured to +45064 bytes past the 20480-byte window, 44367 corrupted bytes, repeated every pass while the wrapped free never drains. Harness exit 42. REC vchan parents are default-config (snd_maxautovchans=16), reachable by an unprivileged user opening /dev/dsp for capture with a multichannel format; content is attacker-paced format-converted capture data. Distinct sink from DF-4849 (which filed the PLAY path :299-368 ring-wrap + mcnt memset bomb); same generator family root. Three-way fix validation: (in-tree generator, unpatched)=exit 42 + smash; (in-tree generator, fix.diff clamp)=exit 0 clean; (fixed generator, unpatched)=exit 0 - the clamp kills the sink independently of the generator fix.",
  "exploit_chain": "unprivileged: open /dev/dsp for capture on a vchan-capable device -> REC vchan parent formats to first childs fmt (e.g. S16_LE 6ch, align 12) while hw side is 2ch -> feed_matrix below the mixer over-returns on a bad-residue first request (bufsize ~16390) -> rl > bufsize, getfree wraps to ~4G -> loop issues maxfeed (~64KB) requests into round_page(bufsize) tmpbuf -> linear heap OOB write of ~45KB past a ~20KB heap object with up-mixed capture content, repeated per pass - kernel heap corruption (would be fatal/corrupting in-kernel; no in-kernel differential attempted on this rig for the same ES1370-without-backend limitation documented in DF-4849).",
  "evidence": "baseline.log / run.log (exit 42: ACQUIRE-OVER: count=16392 free=16390 ... rl -> 16392 + REAR-GUARD SMASHED: window=20480 farthest=45064 smashed_bytes=44367); fixed.log (fixed generator: exit 0, no bad-residue bufsize exists); fixedmixer.log (in-tree generator + fix.diff clamp: exit 0, acquire-over count=0); ab_all.log (three-way); VERDICT.md; fix.diff",
  "kernel_refs": [
    "sys/dev/sound/pcm/feeder_mixer.c:194-204",
    "sys/dev/sound/pcm/feeder_mixer.c:198-199",
    "sys/dev/sound/pcm/feeder_mixer.c:201",
    "sys/dev/sound/pcm/buffer.c:180-195",
    "sys/dev/sound/pcm/buffer.c:519-525",
    "sys/dev/sound/pcm/buffer.c:615-635",
    "sys/dev/sound/pcm/channel.c:425-430",
    "sys/dev/sound/pcm/feeder_chain.c:832-833",
    "sys/dev/sound/pcm/sound.c:62",
    "sys/dev/sound/pcm/feeder_matrix.c:479-509"
  ],
  "poc_changes": "New REC-path harness on the DF-4846..4849 fxshim infrastructure: faithful sndbuf_acquire/getfree/getready (buffer.c semantics WITHOUT KASSERTs, matching the no-INVARIANTS production kernel), ring buffer + kernel-sized round_page tmpbuf window with guards, real feed_matrix as c->feeder->source below the real mixer, only feed_root stubbed (bounded hw budget, writes where told - exactly what real feed_root/sndbuf_dispose does); bad-residue bufsize auto-scan picks a kernel-formable blkcnt*blksz value inside the generator misround window for divisor 12.",
  "attempts": 3,
  "guest_uname": "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",
  "runtime_sec": 600,
  "guest_dirty": 0,
  "build_cmd": "cd /root/fx4952 && sh build.sh   (cc -O1 -g -Wall -o ffrec ffrec.c fxsrc.c)",
  "run_cmd": "./ffrec   # baseline exit 42; sh build.sh /root/fx4952/snd_fxdiv_gen_fixed.awk && ./ffrec -> exit 0; patched-mixer build ./ffrec_fixedmixer -> exit 0",
  "code_hash": "927d6cc91259b52338c516ab0e0391a004290fbb05cd8124dfb27c1b521a0fd2",
  "notes": "Pass-2 follow-up question answered: with the DF-4846-family generator fix, NO other over-return source exists (feed_root returns count/min exactly; feed_format/matrix/volume/eq over-returns were solely the generator misround, A/B-validated 0 failures in the family packs; feed_rate structurally returns count-left) - DF-4849s mcnt underflow is unreachable post-fix, and feed_mixer_rec cannot wrap (fixed-generator scan finds zero bad-residue values in [8194..65534]). The :362 clamp in fix.diff is defense-in-depth; the :200 clamp is the sink fix. Same-family root as DF-4846/47/48/49 (per-file sinks of one generator defect, consistent with how the project filed the family). Guest left clean (no kernel changes; /root/fx4952 harness dir only).",
  "recommended_fix": "Clamp the REC acquire in feed_mixer_rec (cnt = min(cnt, sndbuf_getfree(b)) before sndbuf_acquire) so b->rl can never pass b->bufsize; independently clamp rcnt<=count at :362 (carried in fix.diff); root fix remains the snd_fxdiv_gen.awk generator (DF-4846 family).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Three-way A/B on the guest: in-tree generator + unpatched mixer = exit 42 with 45064-byte rear-guard smash; in-tree generator + fix.diff clamp = exit 0 clean (acquire-over count=0); fixed generator + unpatched mixer = exit 0. The mixer-local clamp kills this sink even while the generator defect is still in the tree. Kernel rebuild A/B not performed (harness-level standard, consistent with the DF-4846..4849 family; the no-INVARIANTS guest cannot produce an in-kernel differential for this rig - documented limitation).",
  "fix_evidence": "fixedmixer.log (in-tree generator + clamped mixer: exit 0, clean guards); baseline.log (unpatched: exit 42 + smash); fixed.log (fixed generator: exit 0); fix.diff"
}