DF-4849 / verdict.json
{ "finding_id": "DF-4849", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "Reproduced at feeder level on the audit guest against the real, unmodified sys/dev/sound/pcm/feeder_mixer.c: (M1) :299 count = SND_FXROUND(count, sz) misrounds PAST the callers free space for bad-residue values inside the claimed domain (first bad 8195 for sz=12; 15-40% of values above first_bad), children fill count > free bytes into b and the mixer returns rcnt > free - in-kernel sndbuf_acquire() (buffer.c:615-636, KASSERT-only) then overflows the ring and inflates rl past bufsize so sndbuf_getfree = bufsize - rl wraps unsigned. 500/500 M1 checks fail. (M2) :339-364 FXROUNDs each childs return; with a child feeder that returns more than asked (proven in-tree on real code by DF-4846 feeder_format and DF-4848 feeder_volume), rcnt > count makes :362 mcnt = count - rcnt UNDERFLOW and :368 executes memset(b + rcnt, zerodata, ~4GiB) - harness caught it verbatim: MEMSET-BOMB: memset(b + 65556, 0x00, 4294967284) (exit 42). Vchan mixing is default (snd_maxautovchans=16, sound.c:62) - any two /dev/dsp opens mix through this path with parent sz set by unprivileged channel ioctls. In-kernel churn (ES1370, 6ch+7ch children, bad-residue pacing) showed no differential anomaly vs the aligned control (channel dead occurs in both - ES1370-without-audiodev artifact), so no in-kernel observation on this no-INVARIANTS rig; the memset would be instantly fatal if its composite conjunction fired.", "exploit_chain": "unprivileged: two /dev/dsp opens (two vchan children on one default-mixed parent) -> multichannel formats give non-pow2 parent sz (e.g. 6ch S16 = 12) -> user-paced drains land free on bad residues >= 8195 -> persistent ring overflow (audio corruption + getfree wrap); with an overshooting child head feeder (feeder_format/feeder_volume per DF-4846/4848) the mcnt underflow arms a ~4GiB kernel memset 12 bytes past the destination scratch - total kernel memory destruction.", "evidence": "mix.log (M1 500/500 rcnt=8196 (+1 bytes past free) + MEMSET-BOMB: memset(b + 65556, 0x00, 4294967284), exit 42); mix_fixed.log (fixed: 0 failures, no bomb); churn_bad.log + churn_dmesg.txt (in-kernel attempt, non-differential); ffmix.c/fxshim.h/fxsrc.c; VERDICT.md", "kernel_refs": [ "sys/dev/sound/pcm/feeder_mixer.c:299", "sys/dev/sound/pcm/feeder_mixer.c:339-364", "sys/dev/sound/pcm/feeder_mixer.c:362", "sys/dev/sound/pcm/feeder_mixer.c:368", "sys/dev/sound/pcm/buffer.c:708-712", "sys/dev/sound/pcm/buffer.c:615-636", "sys/dev/sound/pcm/sound.c:62", "sys/tools/sound/snd_fxdiv_gen.awk:56-71" ], "poc_changes": "Fresh harness (DF-4846 infrastructure) extended with fake struct pcm_channel/snd_dbuf machinery (children.busy list, CHN_FOREACH/CHN_LOCK/CHN_STOPPED, per-child feed control, sndbuf_* shims) and a memset interceptor defined before including the file under test so the ~4GiB call is recorded (exit 42) instead of executed; M2 child overshoot (+8) is the delta class fxfmt produces from real feeder_format code (count 65548 -> 65556).", "attempts": 4, "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": 900, "guest_dirty": 0, "build_cmd": "cd /root/fx && sh build.sh; cc -O1 -g -o ffmix ffmix.c fxsrc.c", "run_cmd": "./ffmix # baseline: exit 42 (memset bomb) + M1 fails; cp snd_fxdiv_gen_fixed.h snd_fxdiv_gen.h; rebuild -> ALL PASS", "code_hash": "", "notes": "Guest booted with -device ES1370 and snd_es137x kldloaded for the churn; reset to clean-source snapshot afterwards. The M2 composite precondition (child feeder returning count+delta) is separately proven on real code - DF-4846 (feeder_format T1: ret=16400 for count=16399) and DF-4848 (feeder_volume T1: ret=32772 for count=32771). Shared table root cause with DF-4846/4847/4848; one generator fix clears all four.", "recommended_fix": "Fix snd_fxdiv_gen.awk (domain-exact reciprocals + 64-bit multiply, fix.diff); independently harden :362 with if (rcnt > count) rcnt = count; so no feeder-contract violation can arm the giant memset.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "A/B at harness level, only the generated header swapped: baseline = 500/500 M1 ring-overflow fails + memset bomb (exit 42); fixed = 0 failures, no bomb (mix.log vs mix_fixed.log). Kernel rebuild A/B not performed (null differential in-kernel baseline on this rig).", "fix_evidence": "mix_fixed.log (0 failures, no bomb); fix.diff; snd_fxdiv_gen_fixed.awk" } |