# 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.
