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)
PoC verification
Evidence pack
findings/poc/DF-3999 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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:2494pcm_register(dev, sc, 1, 2 + sc->adcn)(the designed5is commented out at 2493),envy24ht.c:2499singlepcm_addchan(... PCMDIR_PLAY ...)(the designed loop is commented out at 2498),envy24ht.c:2502-2505rec addchan loop,envy24htchan_initenvy24ht.c:1497num = sc->chnum;andenvy24ht.c:1512ch->num = envy24ht_chanmap[num];.- Slot formula assuming ADC numbering:
envy24ht.c:1432andenvy24ht.c:1462slot = (ch->num - ENVY24HT_CHAN_REC_ADC1) * 2;withENVY24HT_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) andenvy24ht.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 verbatimr32slagainst 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
fixedfix.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
Confirmed kernel references
- sys/dev/sound/pci/envy24ht.c:2494
- sys/dev/sound/pci/envy24ht.c:2499
- sys/dev/sound/pci/envy24ht.c:2502
- sys/dev/sound/pci/envy24ht.c:1497
- sys/dev/sound/pci/envy24ht.c:1512
- sys/dev/sound/pci/envy24ht.c:221
- sys/dev/sound/pci/envy24ht.c:1432
- sys/dev/sound/pci/envy24ht.c:1435
- sys/dev/sound/pci/envy24ht.c:1462
- sys/dev/sound/pci/envy24ht.c:1465
- sys/dev/sound/pci/envy24ht.h:164
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).
No comments yet.