feed_mixer_feed FXROUND past the caller's free space (persistent sndbuf ring overflow) and mcnt = count - rcnt unsigned underflow arming a ~4GiB memset at b+rcnt when any child feeder over-returns
Summary
Entry count rounded past the caller's FREE space (children fill past free; sndbuf_acquire ring overflow, rl>bufsize, getfree wraps) - 500/500 harness checks fail. Worse: each child's return is FXROUNDed; with an over-returning child head (proven in-tree: feeder_format/volume) rcnt>count makes mcnt=count-rcnt underflow and the memset executes at ~4GiB length - caught verbatim ('MEMSET-BOMB: memset(b+65556, 0x00, 4294967284)', exit 42). Vchan mixing default (snd_maxautovchans=16): two /dev/dsp opens hit this. Generator fix + clamp-rcnt hardening.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4849 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| verdict.json | β | 4.6 KB | view raw | |
| fxshim.h | β | 6.9 KB | view raw | |
| fxsrc.c | β | 2.0 KB | view raw | |
| ffmix.c | β | 9.2 KB | view raw | |
| fxfmt.c | β | 6.6 KB | view raw | |
| ffmat.c | β | 5.3 KB | view raw | |
| ffvol.c | β | 5.5 KB | view raw | |
| ffmix.c | β | 9.2 KB | view raw | |
| dspchurn.c | β | 2.1 KB | view raw | |
| ctlchurn.c | β | 991 B | view raw | |
| build.sh | β | 119 B | view raw | |
| run.sh | β | 140 B | view raw | |
| fix.diff | β | 1.0 KB | view raw | |
| snd_fxdiv_gen_fixed.awk | β | 4.0 KB | β download |
DF-4849 β feeder_mixer.c FXROUND ring overflow + mcnt-underflow memset bomb
What this proves
The REAL, UNMODIFIED sys/dev/sound/pcm/feeder_mixer.c:
-
M1 (:299)
count = SND_FXROUND(count, sz)misrounds PAST the caller's free space for bad-residue values inside the claimed domain (first bad 8195 for sz=12). Children fillcount > freebytes intoband the mixer RETURNS rcnt > free β in-kernelsndbuf_acquire(to, tmpbuf, rcnt)then overflows the ring (buffer.c:615-636, KASSERT-only in production;rlinflates pastbufsize,sndbuf_getfree = bufsize - rlwraps unsigned). 500/500 M1 checks fail. -
M2 (:339-364, :362, :368) when a child feeder returns more than asked (proven possible in-tree by DF-4846/DF-4848: feeder_format/feeder_volume return count+delta on the same misrounds),
rcnt = SND_FXROUND(ret, sz)exceeds count andmcnt = count - rcntunderflows to ~0xFFFFfffX, then:368executesmemset(b + rcnt, zerodata, ~4GiB)β catastrophic heap overwrite. The harness catches the exact call:
MEMSET-BOMB: memset(b + 65556, 0x00, 4294967284) [mixer count window was 65544]
(exit code 42). This is a vchan-mixing default path:
snd_maxautovchans = 16 by default β any two /dev/dsp opens mix through
feed_mixer_feed.
Build / run (guest)
cd /root/fx && sh build.sh && ./ffmix # exit=42 (memset bomb), M1 500 fails cp snd_fxdiv_gen_fixed.h snd_fxdiv_gen.h && cc -O1 -g -o ffmix_fixed ffmix.c fxsrc.c ./ffmix_fixed # ALL PASS, no bomb
In-kernel churn artifacts
churn_bad.log / churn_dmesg.txt (ES1370, 6ch+7ch vchan children,
bad-residue-paced writes): no differential anomaly vs the aligned control
(ctlchurn.c) on this no-INVARIANTS production kernel; the observed
"play interrupt timeout, channel dead" occurs equally in the control run
(ES1370-without-audiodev artifact, not attributable to the defect).
Fix
fix.diff (shared generator fix). A/B: baseline M1 500 fails + bomb β fixed
ALL PASS, no bomb. Defensively, :362 should also clamp
(if (rcnt > count) rcnt = count;) so no child misbehavior can ever arm the
giant memset.
DF-4849 β VERDICT
status: reproduced (feeder level, real unmodified code on the audit guest)
primitive: (M1) persistent sndbuf ring overflow β mixer returns more
bytes than the destination has free; (M2) mcnt unsigned underflow arming a
~4GiB memset at b + rcnt β caught verbatim by the harness:
MEMSET-BOMB: memset(b + 65556, 0x00, 4294967284)
confidence: certain (M1 direct on pristine code; M2 mixer logic proven on
pristine code with the child-overshoot precondition demonstrated separately
on real feeder_format/feeder_volume code β DF-4846/DF-4848)
Root cause
feed_mixer_feed (feeder_mixer.c:279-388):
-
:299count = SND_FXROUND(count, sz)β the caller (sndbuf_feed, buffer.c:708) passescount = min(free, maxfeed); for bad-residue free values (first bad 8195 for sz 12; 15β40% of values β₯ first_bad) the generated table misrounds UP (shared snd_fxdiv_gen.awk defect β see DF-4846), so count > free. Children are asked forcountand fill it intob; the mixer returns rcnt = count > free. Kernel side:sndbuf_acquire(to, tmpbuf, rcnt)with rcnt > free copies past the free region (ring overwrite of live audio) and inflatesb->rlpastb->bufsizeβsndbuf_getfree = bufsize - rlthen wraps unsigned (buffer.c:615-636; only KASSERT-protected in production). -
:339-364rcnt = SND_FXROUND(FEEDER_FEED(ch->feeder, ...), sz)β the FXROUND of a child's return. If the child returns more than asked (count + deltaβ exactly what DF-4846 proves feeder_format does and DF-4848 proves feeder_volume does on the same misrounds), rcnt can exceed count, and:362mcnt = count - rcntunderflows (e.g. 65544 β 65556 β 0xFFFFFFF4 = 4294967284).:368then executesmemset(b + rcnt, sndbuf_zerodata(...), mcnt)β a ~4GiB kernel heap memset 12 bytes past the window base β total memory destruction (panic at best).
Reproduction (guest, pristine file)
ffmix (mix.log):
FAIL[M1 ring overflow] count_in(free)=8195 FXROUND=8196 rcnt=8196 (+1 bytes past free) [500/500] M2: child0 (feeder_format-style) returns count+8; FXROUND(65544+8, 12) = 65556 > count 65544 MEMSET-BOMB: memset(b + 65556, 0x00, 4294967284) [mixer count window was 65544 => mcnt underflow]
exit code 42. The M2 child stub returns count+8 β a delta class the fxfmt/ffvol harnesses produce from REAL feeder code (e.g. feeder_format count=65548 β 65556).
Kernel reachability
- Vchan mixing is default:
snd_maxautovchans = 16(sound.c:62); every pair of/dev/dspopens becomes children of one parent mixed by feed_mixer_feed (feeder_mixer.c:314-380). The parent format follows the first child (vchan sync), giving non-pow2 sz (12/14) via unprivileged channel ioctls. - M1 needs only: free landing on a bad residue β₯ 8195 (user-paced drain) β a large fraction of feeds on 8Kβ64K buffers.
- M2 needs the additional conjunction of an overshooting child head feeder (DF-4846/DF-4848-proven) and β₯ 2 busy children β all unprivileged, default-config choices.
In-kernel attempt (honest)
ES1370 + 6ch/7ch vchan children + bad-residue pacing (churn_bad.log,
churn_dmesg.txt): the "channel dead" events occur equally in the ALIGNED
CONTROL run (ctlchurn.c) β an ES1370-without-audiodev emulation artifact,
not differential, so no in-kernel anomaly is attributable to the defect on
this no-INVARIANTS rig β the expected outcome for ring-level overflow
(silent) and the memset (would be instantly fatal if its composite
conjunction fired; it did not on this rig within the bounded churn).
Fix validation
fix.diff (generator): baseline = M1 500/500 fails + memset bomb (exit 42);
fixed = 0 failures, no bomb (mix.log vs mix_fixed.log).
fix_status: fixed. Recommended hardening independent of the generator:
clamp rcnt to count at :362 (if (rcnt > count) rcnt = count;) so no
feeder-contract violation anywhere in the tree can ever arm the giant memset.
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
fixedA/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).
mix_fixed.log (0 failures, no bomb); fix.diff; snd_fxdiv_gen_fixed.awk
Confirmed kernel references
Detail
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 (decisive lines)
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
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).
Verified 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.
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 caller's 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 child's 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.
No comments yet.