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

feed_format_feed sizes its byte window with the inexact generated SND_FXROUND/SND_FXDIV table: misround-up within the claimed [1,65536] domain writes up to oalign-1 bytes past the destination window (or before it via the ialign path) and over-reports the return into sndbuf_acquire (ring overflow)

Summary

The awk generator validates its magic reciprocal at a single point, so the generated table misrounds UP for 15-40% of count values inside its claimed domain (first bad 4694/5473/8195 for align 15/14/12). feed_format consumes it on the caller's window: count rounded past the window (source writes past it; return>count -> ring overflow), the ialign path lands the source before the window, partial-return frames miscount. HARNESS-VERIFIED on the REAL pristine file with the REAL generated header: 8315/28200 canary checks fail; fixed generator: 0. Unpriv default-config via SETFMT on any encoding mismatch. One root cause, one generator fix (DF-4846..4849 family).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4846 Β· 16 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.8 KB ↓ raw
verdict.json β€” 5.1 KB view raw
fxshim.h β€” 6.9 KB view raw
fxsrc.c β€” 2.0 KB view raw
fxfmt.c β€” 6.6 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-4846 β€” feeder_format.c FXROUND align-overshoot (evidence pack)

What this proves

sys/dev/sound/pcm/feeder_format.c sizes its feed window with the build-time generated fast-division macros (SND_FXROUND/SND_FXDIV from sys/tools/sound/snd_fxdiv_gen.awk). The generated table is inexact within its own claimed domain [1, 65536] for every non-power-of-2 divisor: it misrounds UP (FXDIV = floor+1) for 15–40% of count values, starting as low as xβ‰ˆ4694 (align 15), 5473 (align 14), 8195 (align 12). Consequences, each proven by this harness on the REAL, UNMODIFIED file with the REAL generated header:

  • T1 feeder_format.c:220 count = SND_FXROUND(count, info->oalign) can EXCEED the caller's count β†’ the whole pass is sized past the destination window β†’ the source feeder writes past the window (REAR guard hit) and the return value exceeds count (in-kernel: sndbuf_acquire() ring overflow, buffer.c:615-636, only KASSERT-protected).
  • T2 feeder_format.c:233 (ialign>oalign) j = SND_FXROUND(count, ialign) can exceed count β†’ src = dst + count - j < dst (write BEFORE the window) and the source window exceeds the destination.
  • T3 feeder_format.c:240 SND_FXDIV(ret, ialign) on a partial source return miscounts a frame up β†’ the feeder converts and reports more output bytes than the source delivered (return inflation into sndbuf_acquire).

Build / run (guest)

cd /root/fx && sh build.sh      # regenerates snd_fxdiv_gen.h via the real awk,
                                # copies real headers + the 4 feeder files, builds
./fxfmt                         # baseline: FAIL (8315/28200 checks)
cp snd_fxdiv_gen_fixed.h snd_fxdiv_gen.h && cc -O1 -g -o fxfmt_fixed fxfmt.c fxsrc.c
./fxfmt_fixed                   # fixed table: ALL PASS

Expected output

baseline fmt.log: fxfmt: checks=28200 failures=8315 -> FAIL with FAIL[T1 ...] REAR guard (write past window) / return > count lines. fixed fmt_fixed.log: failures=0 -> ALL PASS.

In-kernel reachability (traced, path:line)

  • Chain insert: feeder_chain.c:139-173 (feeder_build_format) on any encoding mismatch β€” ordinary unprivileged SNDCTL_DSP_SETFMT choice.
  • Counts: buffer.c:705-709 maxfeed = SND_FXROUND(SND_FXDIV_MAX, align), count = min(free, maxfeed) β€” free is arbitrary (user-paced), so bad residues β‰₯ first_bad occur routinely on 8K–64K buffers.
  • Return path: buffer.c:712 sndbuf_acquire(to, tmpbuf, cnt) with cnt > free β†’ ring write past free region, rl inflated past bufsize (sndbuf_getfree = bufsize - rl goes unsigned-negative).
  • 64KB-exact tmpbuf (buffer.c:226 kmalloc(round_page(65536))) makes the T1/T2 write a heap OOB write past the allocation.

In-kernel churn (ES1370, 6ch+7ch vchans, bad-residue pacing) showed no differential anomaly vs aligned control on this production (no-INVARIANTS) kernel β€” see DF-4849 pack churn_*.log. Feeder-level proof is decisive.

Fix

fix.diff — fix the generator (snd_fxdiv_gen.awk) so the magic reciprocal is exact across the whole claimed domain (grow shift until (d*mul - 2^z) * 65536 < 2^z) and emit a 64-bit multiply (the required magics no longer fit a 16-bit multiplier). Exhaustively verified for all 44 table divisors over [1,65536]; all four feeder harnesses (format/matrix/ volume/mixer) go FAIL→ALL PASS with the regenerated header.

VERDICT.md
↓ download raw

DF-4846 β€” VERDICT

status: reproduced (feeder level, real unmodified code on the audit guest) primitive: up to oalignβˆ’1 (≀ 14) byte OOB write past the destination window + window-preceding underwrite (ialign>oalign misround) + return value past the caller's count into sndbuf_acquire (ring overflow) confidence: certain (arithmetic defect exhaustively enumerated; feeder behavior proven on the pristine file with the exact kernel-generated table)

Root cause

sys/tools/sound/snd_fxdiv_gen.awk:56-71 (calcdiv) validates its magic reciprocal only at a single point ((y*d) >> z >= 1), not across the claimed domain x = 1..65536. For every non-power-of-2 divisor the emitted SND_FXDIV(x,d) equals floor(x/d) + 1 for a large, exhaustively-enumerated fraction of the domain β€” e.g. FXROUND(5473,14)=5474, FXROUND(8207,24)=8208 (hand-verified: (5473*2341)>>15 = 391, true floor = 390), first bad x per align: 12β†’8195, 14β†’5473, 15β†’4694, 24β†’8207, 28β†’5487; worst overshoot at the 65536 top: 12β†’+8, 14β†’+12, 15β†’+14. This extends DF-4710 (feeder_eq, which characterized only the 65536 boundary) to the whole upper half of the domain β€” ordinary 8K–64K buffer counts, not exotic full-drain events.

feeder_format.c consumes the broken macros on the caller's byte window:

  • :220 count = SND_FXROUND(count, info->oalign) β€” misround β‡’ count' > count. The pass (source ask, in-place conversion tail, dst advance, and the returned dst - b) is then sized for count' β€” past the window the caller (sndbuf_feed, buffer.c:708-712) provided.
  • :233 j = SND_FXROUND(count, ialign) (ialign>oalign, e.g. S32β†’S16 multichannel) β€” misround β‡’ j > count β‡’ src = dst + count - j < dst: the source feeder is asked to fill a window that starts BEFORE the destination buffer.
  • :240 j = SND_FXDIV(FEEDER_FEED(...), ialign) β€” miscount of a partial (user-paced) source return β‡’ frame overcount: the converter reads a frame past the delivered data and returns more bytes than the input warrants (over-report into sndbuf_acquire).

Reproduction (guest, pristine file)

fxfmt (see fmt.log): checks=28200 failures=8315, decisive lines:

FAIL[T1 oalign>ialign] ch=5 in=00500010 out=00501000 count=16399 ret=16400 REAR guard (write past window) maxwrite_off=16400 maxask=8200
FAIL[T3 ret-misround over-conversion] ch=7 in=00701000 out=00700010 count=65520 ret=65506 return > delivered*(oalign/ialign) ...

The canary-guarded window is exactly count bytes (front 0xCB / rear 0xCA guards, checked after every feed); "REAR guard" = the feeder wrote past the window; ret > count = the return over-reports into sndbuf_acquire.

Kernel reachability (each step verified in-tree)

  1. Format feeder inserted on any encoding mismatch β€” feeder_chain.c:139-173 (feeder_build_format), default chain mode, no root gate; the format choice is an unprivileged SNDCTL_DSP_SETFMT/SNDCTL_DSP_CHANNELS on /dev/dsp.
  2. Head feeder invoked with count = min(free, maxfeed) where maxfeed = SND_FXROUND(SND_FXDIV_MAX, align) (buffer.c:705) β€” free is arbitrary user-paced, bad residues β‰₯ first_bad occur on every 8K–64K buffer with ~15–40% probability per feed.
  3. Non-pow2 aligns are ordinary multichannel formats: 5ch/6ch/7ch S16/S24/S32 (align 10/12/14/15/20/21/24/28).
  4. Overshoot lands past to->tmpbuf (heap OOB when the allocation is exact, buffer.c:226 kmalloc(round_page(bufsize)) at 64K geometry) and/or inflates the ring (sndbuf_acquire, buffer.c:615-636, KASSERT-only in production; sndbuf_getfree = bufsize - rl wraps unsigned).

In-kernel attempt (honest)

ES1370 + two vchan children (6ch, 7ch S16) + bad-residue-paced writes: no differential anomaly vs the aligned control (production kernel, no INVARIANTS; the write is ≀ 14 bytes past a kmalloc and the ring overflow is silent) β€” identical outcome to DF-4710's in-kernel attempt, and expected for the same reasons. Logs in the DF-4849 pack.

Fix validation

fix.diff (generator): domain-exact reciprocals + 64-bit multiply. Exhaustive host check: all 44 table divisors exact over [1,65536]. Guest A/B: baseline fxfmt 8315 failures vs fxfmt_fixed 0 failures (ALL PASS) (fmt.log vs fmt_fixed.log). fix_status: fixed.

Not re-reported

DF-4710 (feeder_eq, root-gated EQ) is the same idiom family in a different file; DF-4601/4602 (feeder_chain) unrelated. This finding covers feeder_format.c only.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/B validated at the level the defect was reproduced: identical harness, only the generated header swapped (baseline table vs fixed-generator table) - 8315 failures vs 0 (fmt.log vs fmt_fixed.log). Fixed table exhaustively verified exact for all 44 divisors over [1,65536]. Kernel rebuild A/B not performed: the in-kernel baseline observation on this rig was null (no INVARIANTS, <=14-byte write past kmalloc has no detector), so a rebuilt kernel had no observable to compare - same standard as DF-4710.

fmt_fixed.log (failures=0 ALL PASS); fix.diff; snd_fxdiv_gen_fixed.awk
↓ fix.diffper-fix-DF-4846

Confirmed kernel references

Detail

Evidence (decisive lines)

fmt.log (baseline: checks=28200 failures=8315, 'REAR guard (write past window)' + 'return > count' + T3 over-conversion lines with exact counts/returns); fmt_fixed.log (fixed table: failures=0 ALL PASS); fxfmt.c/fxshim.h/fxsrc.c (harness compiling the pristine feeder_format.c with the real pcm.h/intpcm.h/g711.h/matrix.h headers and the real awk-generated snd_fxdiv_gen.h); VERDICT.md (full path:line chain)

PoC changes

PoC authored fresh in pass 2 (FXROUND follow-up lead from DF-4710): userland harness compiling the pristine feeder_format.c verbatim (#ifdef _KERNEL-gated file) with real kernel headers copied in and the real generated snd_fxdiv_gen.h. Three harness iterations: (1) arena bounds initially refused guard-region writes, masking violations - fixed to allow writes anywhere inside the guarded allocation so the canaries detect them; (2) source stub did not dry up (ready never decremented) - fixed; (3) volume feeder needed desc->type=FEEDER_VOLUME and matrix ids had to be the real matrix.h values.

Verified recommended fix

Fix snd_fxdiv_gen.awk to emit domain-exact reciprocals ((dmul-2^z)65536 < 2^z) with a 64-bit multiply in SND_FXDIV (fix.diff); optionally also clamp each feeder's count to <= the caller's window.

Verdict

Reproduced at feeder level on the audit guest against the real, unmodified sys/dev/sound/pcm/feeder_format.c: the build-time generated SND_FXDIV/SND_FXROUND table (sys/tools/sound/snd_fxdiv_gen.awk) is inexact WITHIN its claimed [1,65536] domain for every non-power-of-2 divisor (misrounds up from x=4694/5473/8195 for align 15/14/12; 15-40% of all counts), and feed_format_feed() sizes its pass with those macros: :220 FXROUND(count,oalign) can exceed the caller's count so the source writes past the destination window and the return over-reports into sndbuf_acquire (ring overflow, buffer.c:615-636 KASSERT-only); :233 FXROUND(count,ialign) misrounds make j>count so src=dst+count-j lands BEFORE the window; :240 FXDIV of a user-paced partial source return miscounts a frame (over-conversion). Harness on pristine code with the real generated header: 8315/28200 checks fail with canary-guard hits; with the fixed generator (fix.diff: domain-exact reciprocals + 64-bit multiply) 0 failures. In-kernel reachability fully traced (feeder_chain.c:139-173 default insert on any encoding mismatch; buffer.c:705-709 arbitrary free counts; multichannel formats are unprivileged DSP ioctls); in-kernel churn (ES1370, 6ch+7ch vchans, bad-residue pacing) shows no differential anomaly vs aligned control on this no-INVARIANTS production kernel, as expected for a <=14-byte write past a kmalloc plus a silent ring overflow - same outcome and reasoning as DF-4710. Impact ceiling: 4-14-byte kernel heap OOB write past tmpbuf at exact-page allocation geometry + persistent sndbuf ring invariant breakage (rl inflated past bufsize; sndbuf_getfree=bufsize-rl wraps unsigned), unprivileged, default config.