# DF-4617 — hdacc.c: unvalidated controller-derived stream tag indexes `codec->streams[dir][stream]` (OOB read → wild indirect call; NULL-write on free path)

File: `sys/dev/sound/pci/hda/hdacc.c`
Lines: 702-710 (`hdacc_stream_intr`), 663-670 (`hdacc_stream_free`)

## What this is

`struct hdacc_softc` holds `device_t streams[2][16]` (hdacc.c:57) — valid stream
tags are 1..15 (HDA 4-bit tag field), dir is 0/1. Both hdacc sink functions
index that array with the `stream`/`dir` values handed up by the hdac
controller driver **without any bounds validation**:

* `hdacc_stream_intr()` — hdacc.c:708 `codec->streams[dir][stream]` (read →
  indirect method call through the loaded `device_t`), invoked from
  `hdac_one_intr()` hdac.c:392-394 with `sc->streams[i].stream`.
* `hdacc_stream_free()` — hdacc.c:668 `codec->streams[dir][stream] = NULL`
  (write), invoked from hdaa.c:2156 with `ch->sid`.

The tag handed up by `hdac_stream_alloc()` (hdac.c:1932-1942) is *computed
from GCAP controller capabilities*, and the BSS branch carries the known
DF-4115 sign error: `stream = 15 - (ss - num_iss + num_oss)` = `15 - 2*num_oss - j`,
which goes **negative** whenever `2*num_oss + j > 15` (any controller with
OSS ≥ 8, or OSS=4 + BSS index j ≥ 8, etc.). `hdac_stream_alloc` stores the
negative tag and a non-NULL `dev` in `sc->streams[ss]` regardless of the
return value (hdac.c:1940-1942); hdaa rejects `sid <= 0` (hdaa.c:2170) but
the stale slot stays populated, so any stream-interrupt bit for that index
dispatches `hdacc_stream_intr(dev, dir, negative_tag)` →
`codec->streams[dir][negative]` reads memory before the array (cad / lock /
dev softc fields and further out for deeply negative tags) as a `device_t`
and tail-calls `HDAC_STREAM_INTR(child, ...)` through it — a wild indirect
call primitive (certain crash; attacker-uncontrolled target).

Threat model: malicious or faulty HDA controller (PCI/Thunderbolt hot-plug
audio; VM/emulator defect) reporting BSS > 0 with a tag config that yields
negative tags, then asserting a stream interrupt for the affected index.
Note a fully malicious controller already owns bus-master DMA, so the
practical delta is *buggy*-controller fault injection; severity Low.

## Verification (this pack)

* Guest boots `hdac0` (Intel ICH6, QEMU) → `hdacc0` → `hdaa0` → `pcm0`.
* Runtime GCAP caps captured by DF-4115 on this same guest:
  `hdac0: Caps: OSS 4, ISS 4, BSS 0` → `num_ss = 8`, `ss ∈ 0..7`,
  BSS branch (`ss >= num_iss + num_oss == 8`) is **structurally unreachable**
  on this guest — the negative-tag feeder cannot fire, so the OOB sink cannot
  be triggered here. Same hardware gate as DF-4115.
* Happy-path exercise: 320 KB of PCM written to `/dev/dsp` (alloc →
  `codec->streams[dir][tag∈1..4]` → interrupt dispatch) — clean, no panic,
  no "Unexpected unsolicited response" spam.

## Build / Run

Nothing to build for the negative-result run:

    ./run.sh     # on the host: drives vm.sh; shows codec attach + dsp playback

Expected: playback completes, guest stays up — the vulnerability is
hardware-gated (BSS > 0 required) and does **not** trigger on this guest.

## Fix

`fix.diff` clamps both sinks (reject dir∉{0,1}, stream∉[1,15] for intr;
[0,15] for free). Verified `git apply --check` clean against the repo root.
Not applied — `sys/` is read-only for the audit. The real fix for the feeder
is the DF-4115 upstream one-token correction in hdac.c.
