nm_init() programs ring/coeff buffers from unvalidated card-provided buftop β cbuf/rbuf/pbuf/acbuf wrap to ~4G offsets used by nm_wrbuf() and sndbuf_setup()
Summary
The card can override buftop with any 32-bit value from its signature block; the four buffer offsets derive by unguarded subtraction, so a buftop below the true minimum wraps them all to ~0xffffxxxx - nm_loadcoeff bus_space-writes coefficients at cbuf+i and the DSP ring maps at pbuf/rbuf, so every user write() memcpys ~4G past the mapping. Card-steerable wild kernel write; physical-attacker/hardware-fault class. Userland wrap-arithmetic prover in the pack. NM_BUFTOP_MIN range-check fix (builds -Werror on guest).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4611 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 802 B | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| buftop_prover.c | β | 2.4 KB | view raw | |
| build.sh | β | 109 B | view raw | |
| run.sh | β | 99 B | view raw | |
| build.log | β | 8.1 KB | view raw | |
| run.log | β | 736 B | view raw | |
| env.txt | β | 1.0 KB | view raw | |
| fix.diff | β | 1.0 KB | view raw |
DF-4611 β nm_init() programs ring/coeff buffers from unvalidated card-provided buftop: cbuf/rbuf/pbuf/acbuf wrap to ~0xffffxxxx and later nm_wrbuf()/sndbuf_setup() write at wild offsets.
BUILD (guest, portable userland) cc -O -Wall -o /tmp/buftop_prover /tmp/buftop_prover.c
RUN (guest, any user) /tmp/buftop_prover
EXPECTED Table of hostile buftop values β wrapped offsets, e.g. buftop=0xffffed00 pbuf=0xffff1d00 "WRAPS -> ~4G OOB bus_space offset" plus the derived minimum sane buftop 0x1b960. Full output: run.log. Kernel-side trigger needs real NM256 hardware (absent: env.txt).
FIX VALIDATION (build-only, on guest /usr/src copy) cd /usr/src && patch -p1 < fix.diff cd sys/dev/sound/driver/neomagic && make # -Werror, clean (see build.log)
DF-4611 β nm_init() programs ring/coeff buffers from unvalidated card-provided buftop β wrapped (β4G) bus_space offsets
File: sys/dev/sound/pci/neomagic.c β pass 2 (GLM 5.3), 2026-09-22.
THE BUG
nm_init() (neomagic.c:541) derives every buffer offset from sc->buftop,
and lets the card override it with an arbitrary 32-bit value read out of
its buffer-memory signature block:
562: sc->buftop = (nm_rd(sc, 0xa0b, 2)? 6144 : 4096) * 1024; /* ZX */
571: ofs = sc->buftop - 0x0400;
572: sc->buftop -= 0x1400;
576: if ((nm_rdbuf(sc, ofs, 4) & NM_SIG_MASK) == NM_SIGNATURE) {
577: i = nm_rdbuf(sc, ofs + 4, 4);
578: if (i != 0 && i != 0xffffffff) {
581: sc->buftop = i; <-- unvalidated card value
582: }
583: }
585: sc->cbuf = sc->buftop - NM_MAX_COEFFICIENT; /* -0x5000 */
586: sc->rbuf = sc->cbuf - NM_BUFFSIZE; /* -0x4000 */
587: sc->pbuf = sc->rbuf - NM_BUFFSIZE;
588: sc->acbuf = sc->pbuf - (NM_TOTAL_COEFF_COUNT * 4); /* -0xC560 */
A sane buftop is 4β6 MB, so nothing checks the arithmetic. A defective or
hostile card returning a small i makes cbuf/rbuf/pbuf/acbuf wrap to
~0xffffxxxx: every later
nm_wrbuf(sc, sc->cbuf + i, coefficients[ofs+i], 1)for i < 0x5000 (nm_loadcoeff, neomagic.c:314 β runs at the first setformat/speed), andsndbuf_setup(b, rman_get_virtual(sc->buf) + chnbuf, NM_BUFFSIZE)(nmchan_init, neomagic.c:362 β makes every user write() memcpy ~4 GB past the BAR mapping)
β¦becomes a wild kernel-side write at a card-chosen offset below/around the
mapping. Minimum legitimate buftop = 0x1400+0x5000+2Γ0x4000+0xC560 =
0x1b960; see buftop_prover.c output in run.log for the wrapped
offsets produced by hostile values (e.g. buftop=0x100 β pbuf=0xffff1d00).
THREAT
Requires physical NeoMagic NM256 hardware whose buffer signature block
returns a crafted i (defective NVRAM, or a hostile PCIe device spoofing
the 10c8:8005/8006 IDs). Attacker class = malicious/failed hardware, not
an unprivileged user; impact = kernel memory corruption at attach/first
use (steerable-offset wild write), realistically a panic.
VERIFICATION STATUS: untested (hardware-gated)
No NM256 device model exists in QEMU 7.2 (env.txt), so the kernel-side
write cannot be triggered on the audit guest. The arithmetic is proven by
the userland prover (buftop_prover.c, run.log) and the code path by the
citations above (confidence: certain for the defect; speculative for
real-world exploitation).
- fix build validation:
fix.diff(range-check buftop β₯ 0x1b960 before use) applied to the guest's /usr/src and snd_neomagic.ko rebuilt with-Werrorβ clean (build.log).
FIX
fix.diff: reject implausible buftop (< 0x1b960) with a diagnostic and
attach failure β same defensive posture DF-1371 established for
firmware-misreported geometry.
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_testablefix.diff adds the NM_BUFTOP_MIN sanity check right before the cbuf/rbuf/pbuf/acbuf derivation and returns -1 (attach fails cleanly) on violation; git apply --check clean and snd_neomagic.ko rebuilds with -Werror on the guest (build.log). Runtime behaviour is not observable on the audit guest: no NM256 function exists there.
findings/poc/DF-4611/fix.diff; findings/poc/DF-4611/build.log (patched module compile+link, -Werror)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4611/VERDICT.md; buftop_prover.c + run.log (hostile buftop -> wrapped offsets table); env.txt (no NM256 on guest); fix.diff + build.log (validated buftop check, module builds clean with -Werror)
PoC changes
authored fresh prover (no seed); userland replication of the kernel arithmetic since the kernel-side trigger needs absent hardware
Verified recommended fix
Range-check sc->buftop (>= 0x1400+0x5000+2NM_BUFFSIZE+NM_TOTAL_COEFF_COUNT4) after the card override and fail attach on implausible values
Verdict
nm_init() (neomagic.c:541) lets the card override sc->buftop with an arbitrary 32-bit value from its buffer signature block (neomagic.c:576-582) and then derives cbuf/rbuf/pbuf/acbuf by subtraction (neomagic.c:585-588) with no range validation. Minimum legitimate buftop is 0x1400+0x5000+2*0x4000+0xC560 = 0x1b960; a defective or hostile card returning a small i (e.g. 0x100) wraps all four offsets to ~0xffffxxxx, after which nm_loadcoeff's nm_wrbuf(sc, sc->cbuf+i, ...) loop (neomagic.c:314, first setformat/speed) and nmchan_init's sndbuf_setup(rman_get_virtual(sc->buf)+chnbuf, NM_BUFFSIZE) (neomagic.c:362, every user write()) operate at wild bus_space/kernel offsets ~4G past the BAR mapping -- card-steerable kernel OOB write at attach/first use. Untested rather than demonstrated: triggering needs real NM256 hardware whose signature block returns a crafted value; QEMU 7.2.22 has no NM256 device model (env.txt). The wrap arithmetic is reproduced exactly by the userland prover (run.log: buftop=0x100 -> pbuf=0xffff1d00 etc.), and the code path is source-proven. Confidence: certain for the defect (missing validation is unconditional), speculative for real-world exploitation.
No comments yet.