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

Record channels are bound to PLAY hw numbers by the 1-play-channel registration: slot = (num-5)*2 yields -8/-6/-4/-2 and +8/+10 against the 2-entry rec frame, so every EMLDMARD reads kernel memory from BEFORE and PAST the record DMA buffer β€” unconditional at offset 0 on the FIRST record channel

Summary

pcm_register(dev, sc, 1, 2+adcn) plus the single pcm_addchan consume envy24ht_chanmap[] positionally: rec channels get PLAY hw numbers, never the REC_ADC1..4 the record emulators assume. slot = (ch->num-5)*2 = -8,-6,-4,-2,+8,+10 against the 2-entry rec frame: dmabuf[src*2+slot] with negative indices reads up to 32 bytes BEFORE rbuf into the user's stream on every record pass at offset 0, and slot +10 reads up to 40 bytes PAST (5th rec channel, standard adcn=3 boards). Distinct root cause from DF-3998 (present on the first channel with no offset); also breaks record routing entirely. Harness-proven; slot-clamp fix validated (0 leaks).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3999 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
emldmaht_harness.c β€” 16.1 KB view raw
emldmaht_harness_fixed.c β€” 16.4 KB view raw
build.sh β€” 111 B view raw
run.sh β€” 102 B view raw
build.log β€” 738 B view raw
run.log β€” 999 B view raw
run.2.log β€” 999 B view raw
fix_run.log β€” 1.5 KB view raw
env.txt β€” 218 B view raw
fix.diff β€” 1.0 KB view raw
verdict.json β€” 3.5 KB view raw

DF-3999 β€” Record channels mis-mapped to play hw numbers: OOB rbuf reads before/after the buffer on every record pass

File: sys/dev/sound/pci/envy24ht.c (pass 2) Class: kernel info leak (unprivileged, deterministic) + broken record routing.

Reproduce

scp -F dfbsd-qemu/config emldmaht_harness.c dfbsd-maxx:/tmp/
vm.sh run_user 'cc -O2 -Wall -o /tmp/emldmaht_harness /tmp/emldmaht_harness.c && /tmp/emldmaht_harness'

Expected (baseline) β€” cases [A]/[B]

[A] 1st rec ch (num=1, slot=-8, offset=0), r32sl:
  [A] user stream received 8 samples from BEFORE rbuf, 0 from PAST rbuf end
         first leaked: data[0] = 0xabadbe08 (kernel-heap marker)
[B] 5th rec ch (num=10, slot=+10, offset=0), r32sl:
  [B] user stream received 0 samples from BEFORE rbuf, 10 from PAST rbuf end
         first leaked: data[8182] = 0xdeadfe00 (kernel-heap marker)

Fixed variant (emldmaht_harness_fixed.c, slot clamp applied): both cases 0.

Files

Same harness as DF-3998 (cases [A]/[B] are this finding); fix.diff is the slot clamp. Live /dev/dsp trigger hardware-gated (env.txt: envy24-pci-devs=0).

VERDICT.md
↓ download raw

DF-3999 β€” VERDICT

Finding: envy24ht_pci_attach registers 1 play + (2+adcn) rec channels (envy24ht.c:2494-2505) but consumes the shared 11-entry envy24ht_chanmap[] (envy24ht.c:221-233) positionally: the single play channel gets hw num chanmap[0]=0 (PLAY_DAC1) and the rec channels get chanmap[1..2+adcn] = play channel numbers 1,2,3,4 followed by REC_MIX(10), REC_SPDIF(9) β€” never the REC_ADC1..4 numbers (5..8) the record emulators assume. envy24ht_r32sl/envy24ht_r16sl compute slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2 (envy24ht.c:1432, 1462; REC_ADC1 = 5), producing negative slots (βˆ’8,βˆ’6,βˆ’4,βˆ’2 for the first four rec channels) and slots β‰₯ +8 (+10, +8 for the 5th/6th) against a 2-entry interleaved frame (ENVY24HT_REC_CHNUM=2, rbuf = 8192 sample32_t entries). Result: every EMLDMARD on those channels reads kernel memory from BEFORE the record DMA buffer (negative index, up to 32 bytes before sc->rbuf) and/or past its end β€” unconditionally, at offset 0, on the FIRST record channel open β€” and copies it into the user's record stream.

Status: reproduced (verbatim-code harness on the guest; live trigger hardware-gated β€” no ICE1724 device in QEMU). Confidence: certain. Impact class on real hardware: kernel info leak (32 bytes before the 32 KB bus_dmamem_alloc rbuf + 40 bytes past its end per pass on the standard adcn=3 configuration), unprivileged, deterministic, on every record DMA pass of the affected channels. Independent of DF-3998 (present at offset 0), though DF-3998's offset widens the window.

Root cause (path:line)

  • Registration/mapping mismatch:
  • envy24ht.c:2494 pcm_register(dev, sc, 1, 2 + sc->adcn) (the designed 5 is commented out at 2493),
  • envy24ht.c:2499 single pcm_addchan(... PCMDIR_PLAY ...) (the designed loop is commented out at 2498),
  • envy24ht.c:2502-2505 rec addchan loop,
  • envy24htchan_init envy24ht.c:1497 num = sc->chnum; and envy24ht.c:1512 ch->num = envy24ht_chanmap[num];.
  • Slot formula assuming ADC numbering: envy24ht.c:1432 and envy24ht.c:1462 slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2; with ENVY24HT_CHAN_REC_ADC1 = 5 (envy24ht.h:164) while the rec channels actually carry num ∈ {1,2,3,4,10,9} β†’ slot ∈ {βˆ’8,βˆ’6,βˆ’4,βˆ’2,+10,+8}.
  • Accesses: envy24ht.c:1435-1436 (r32sl) and envy24ht.c:1465-1466 (r16sl): dmabuf[src * 2 + slot{,+1}].

For the standard board configuration (scfg=0x0b β†’ adcn=3 β†’ 5 rec channels, hw nums {1,2,3,4,10} β†’ slots {βˆ’8,βˆ’6,βˆ’4,βˆ’2,+10}): - rec channels 1-4 (slots βˆ’8..βˆ’2): for src < 5 the index src*2 + slot is negative β†’ reads up to 32 bytes BEFORE sc->rbuf (kernel heap preceding the DMA allocation) into the user stream, on every EMLDMARD pass, at offset 0, starting with the first channel. - rec channel 5 (slot +10): for src β‰₯ 4091 the index exceeds the last frame β†’ reads up to 40 bytes PAST the rbuf end, every pass. - The E2PROM path (envy24ht_rom2cfg, arbitrary scfg byte) allows adcn=4 β†’ 6 rec channels β†’ additionally slot +8 (num 9).

Reproduction

  • emldmaht_harness.c (shared with DF-3998) runs the verbatim r32sl against canary arenas sized exactly like rbuf:
  • [A] first rec channel (num=1, slot=βˆ’8, offset=0): 8 samples from BEFORE rbuf land in the user stream (data[0] = 0xabadbe08 β€” full 32-bit kernel-heap marker words with S32_LE) β€” run.log.
  • [B] 5th rec channel (num=10, slot=+10, offset=0): 10 samples from PAST the rbuf end (data[8182] = 0xdeadfe00) β€” run.log.
  • Deterministic across run.2.log; identical arithmetic to what the kernel runs at every record interrupt.
  • Fixed variant (slot clamp % ENVY24HT_REC_CHNUM, plus the DF-3998 index wraps): [A]/[B] report 0 leaked samples (fix_run.log).

Impact assessment

Unprivileged kernel heap disclosure, limited per-pass window (~72 bytes around rbuf) but repeating on every record DMA block and across all affected channels, complementary to DF-3998's larger window. Also a functional defect: all record channels are mis-routed (their PCM comes from the wrong slots / out of range), so recording on this hardware is broken in general β€” consistent with the driver being rarely exercised.

Fix

Minimal safety fix (fix.diff): clamp the record slot into the frame: slot = ((ch->num - ENVY24HT_CHAN_REC_ADC1) * 2) % ENVY24HT_REC_CHNUM; The full functional fix is restoring the designed registration (5 play channels, so rec channels consume chanmap[5..] = REC_MIX, REC_SPDIF, REC_ADC1..) together with a slot computation bounded to [0, ENVY24HT_REC_CHNUM); note even the designed mapping yields slots {+10,+8,0,2,4}, so the clamp is required either way for REC_MIX/SPDIF. Validated at harness level (fix_run.log: 0 leaks).

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

fix.diff (slot clamp) applied to the verbatim harness together with the DF-3998 index wraps: baseline case A 8 before-rbuf leaks + case B 10 past-end leaks; patched run reports 0 leaks in both cases (fix_run.log). No kernel build (no envy24ht hardware on the guest); the clamp is compile-trivial.

findings/poc/DF-3999/fix_run.log; findings/poc/DF-3999/emldmaht_harness_fixed.c; findings/poc/DF-3999/fix.diff
↓ fix.diffper-fix-DF-3999

Confirmed kernel references

Detail

Exploit chain

open /dev/dsp0.r0 (0666 clone) and read(): every record DMA block leaks 32 bytes of kernel heap preceding sc->rbuf (and, on the 5th rec channel, 40 bytes past it) into the user's PCM stream; repeatable indefinitely -> unprivileged kernel heap disclosure. Live chain not executed: guest lacks the PCI device (hardware-gated).

Evidence (decisive lines)

run.log cases [A] (8 samples from BEFORE rbuf, full 32-bit marker words) and [B] (10 samples from PAST rbuf end); run.2.log deterministic; fix_run.log (slot clamp: 0 leaks); VERDICT.md mapping analysis

PoC changes

Shares the DF-3998 verbatim harness (cases [A]/[B] added for this finding); fixed variant carries the slot clamp % ENVY24HT_REC_CHNUM.

Verified recommended fix

Clamp the record slot into the 2-entry frame: slot = ((ch->num - ENVY24HT_CHAN_REC_ADC1) * 2) % ENVY24HT_REC_CHNUM in r32sl/r16sl; structurally, register the designed 5 play channels so rec channels consume chanmap[5..] (see fix.diff)

Verdict

Reproduced via verbatim-code harness on the guest (hardware-gated live trigger: no ICE1724 device in QEMU). envy24ht_pci_attach registers 1 play + (2+adcn) rec channels (envy24ht.c:2494-2505) but the rec channels consume chanmap[1..] which are PLAY hw numbers (1,2,3,4) and REC_MIX/SPDIF (10,9); the record emulators compute slot=(ch->num-5)*2 giving slots -8..-2 and +8/+10 against a 2-entry frame. Every EMLDMARD on the first rec channel reads 8 sample32 entries (32 bytes) from BEFORE the 32KB record DMA buffer into the user's record stream at offset 0 (harness case A: data[0]=0xabadbe08), and the 5th rec channel reads 10 entries (40 bytes) PAST its end (case B: data[8182]=0xdeadfe00) - unconditional, deterministic, unprivileged on real hardware. Deterministic across runs; slot clamp validated at harness level (0 leaks).