hdacc_stream_intr/hdacc_stream_free index codec->streams[dir][stream] with an unvalidated controller-derived stream tag β negative DF-4115 BSS tags dispatch through an OOB-read device_t (wild indirect call)
Summary
streams[2][16] is indexed directly with dir/stream handed up by the controller; the DF-4115 BSS sign error can produce negative tags (down to -45 for OSS=15) which hdac stores with a non-NULL dev - hdaa rejects sid<=0 but the poisoned slot stays populated, so any stream-interrupt bit for that index dispatches stream_intr with a negative stream: -2 reads codec->lock (non-NULL) -> method dispatch on a struct lock* -> certain crash; deeper tags read outside the softc into a garbage device_t -> wild indirect call. This corrects DF-4115's no-consequence assessment and adds the missing sink clamp. Guest's QEMU ICH6 reports BSS 0 so the branch is structurally unreachable there (320KB playback clean). Clamp-both-sinks + fix-the-feeder fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4617 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 5.0 KB | β raw | |
| run.sh | β | 89 B | view raw | |
| dmesg.txt | β | 770 B | view raw | |
| fix.diff | β | 878 B | view raw | |
| verdict.json | β | 3.5 KB | view raw |
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:708codec->streams[dir][stream](read β indirect method call through the loadeddevice_t), invoked fromhdac_one_intr()hdac.c:392-394 withsc->streams[i].stream.hdacc_stream_free()β hdacc.c:668codec->streams[dir][stream] = NULL(write), invoked from hdaa.c:2156 withch->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.
DF-4617 VERDICT β unvalidated stream tag in hdacc_stream_intr / hdacc_stream_free
STATUS: not_reproduced β live trigger hardware-gated (requires an HDA controller with BSS > 0 whose DF-4115 tag arithmetic yields negative tags; this guest's controller reports BSS = 0). reproduced: 0 Β· impact: none on this guest Β· confidence in the code mechanics: certain (source-traced, line by line, below).
The defect (source truth)
struct hdacc_softc (hdacc.c:53-61):
device_t streams[2][16]; /* valid tag index: 1..15 */ device_t tags[64]; /* valid unsol tag: 0..63 */
Sink 1 β hdacc_stream_intr (hdacc.c:702-710), the reachable one
708: if ((child = codec->streams[dir][stream]) != NULL) 709: HDAC_STREAM_INTR(child, dir, stream);
Caller: hdac_one_intr() (hdac.c:386-396) loops over all num_ss stream
slots and, for any set INTSTS bit with sc->streams[i].dev != NULL, calls
HDAC_STREAM_INTR(dev, sc->streams[i].dir, sc->streams[i].stream);
sc->streams[i].stream is set in hdac_stream_alloc() (hdac.c:1932-1942)
from controller-GCAP-derived arithmetic. The BSS branch (hdac.c:1934)
carries the known DF-4115 sign error:
stream = 15 - (ss - sc->num_iss + sc->num_oss); /* = 15 - 2*num_oss - j */
which is negative whenever 2*num_oss + j > 15 (j = BSS index; e.g. any
controller with OSS β₯ 8, or OSS=4 with a 9th+ BSS stream). hdac stores the
negative tag and sc->streams[ss].dev = child unconditionally
(hdac.c:1940-1942). hdaa_channel_start rejects the returned sid <= 0
(hdaa.c:2170-2171), but the poisoned slot remains dev != NULL, so any
stream-interrupt bit for that index dispatches into hdacc_stream_intr with a
negative stream:
- stream = -1 β reads the softc
cad+padding word (0) β NULL child β silently skipped (masked); - stream = -2 β reads
codec->lock(non-NULL!) βHDAC_STREAM_INTRmethod dispatch on astruct lock *β wild indirect call, certain crash; - more negative (num_oss=15 β down to 15-30-30 = -45) β reads far outside
the softc β semi-controlled garbage
device_tβ wild call.
No bounds check exists at the sink β hdacc.c:708 is the missing clamp.
Sink 2 β hdacc_stream_free (hdacc.c:663-670)
668: codec->streams[dir][stream] = NULL;
Caller passes ch->sid (hdaa.c:2156-2157), which hdaa only ever sets to an
alloc return it validated > 0 β so the NULL-write at a negative index is
currently unreachable through hdaa; the clamp is defense-in-depth.
Why not reproduced on this guest
- Guest controller: QEMU Intel ICH6 (8086:2668),
hdac0@pci0:0:3:0. - Runtime caps (boot -v, captured on this same guest image by DF-4115,
findings/poc/DF-4115/caps.txt):hdac0: Caps: OSS 4, ISS 4, BSS 0. num_ss = 8,ss β 0..7; the BSS branch requiresss >= num_iss + num_oss = 8β structurally unreachable; every computed tag is in 1..4. No userspace action can make QEMU's ICH6 report BSS > 0, and no syscall path feeds the tag from user memory.- Live exercise (this run,
dmesg.txt):hdac0 β hdacc0 β hdaa0 β pcm0attach; 320 KB PCM β/dev/dsp(RC=0) exercising stream_alloc / streams[dir][1..4] / stream_intr dispatch β no panic, no unsol-tag spam, guest healthy.
This mirrors DF-4115's own verdict on the same hardware gate.
Exploit chain (on affected hardware β hostile/faulty controller)
- Controller reports GCAP with BSS > 0 and 2*num_oss + j > 15 for some BSS index j (e.g. OSS=8/BSS>0, or OSS=4/BSSβ₯9).
- Audio open exhausts ISS/OSS or requests dir fallback β hdac_stream_alloc picks BSS slot β negative tag stored with dev != NULL (hdac.c:1940-42).
- Controller asserts the stream's INTSTS bit (hostile device trivially can; a buggy one may via tag-collision chaos).
- hdac_one_intr β hdacc_stream_intr β OOB read at streams[dir][negative]
β wild
HDAC_STREAM_INTRindirect call β kernel crash / unreliable control-flow hijack (call target is the OOB-read pointer, not directly attacker-chosen; a malicious controller already has DMA, so this is fault-injection amplification, not a new privilege boundary).
Ceiling: kernel memory-corruption-class primitive gated on non-default audio controller hardware β Low severity, certain mechanics.
Fix
fix.diff (git-apply-able, checked): clamp dir/stream at both sinks.
The true root cause is DF-4115's hdac.c arithmetic β fix upstream there;
the hdacc clamps make the sink safe against any future feeder bug.
fix_status: not_testable β the trigger is hardware-gated off on this guest (BSS=0), so baseline-vs-patched behavior cannot be observed on the standard snapshot; the clamp compiles and the happy path (playback) is unaffected.
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_testableTrigger hardware-gated off on this guest (GCAP BSS=0) β no baseline misbehavior exists to compare; fix.diff is git-apply-able and the patched-tree happy path is unchanged by construction (clamps only reject invalid indices that cannot occur on this hardware).
fix.diff; git apply --check clean; playback battery in dmesg.txt
Confirmed kernel references
Detail
Exploit chain
hostile/faulty HDA controller reports GCAP BSS>0 with 2*num_oss+j>15 -> hdac_stream_alloc stores negative tag with dev!=NULL -> controller asserts that stream's INTSTS bit -> hdac_one_intr (hdac.c:392-394) -> hdacc_stream_intr indexes streams[dir][negative] -> OOB-read device_t -> HDAC_STREAM_INTR wild indirect call. Hardware-gated; unreachable on BSS=0 controllers.
Evidence (decisive lines)
VERDICT.md (full path:line chain); dmesg.txt (guest codec chain + playback RC=0); fix.diff (sink clamps, git apply --check clean); findings/poc/DF-4115/caps.txt ('hdac0: Caps: OSS 4, ISS 4, BSS 0' on this same guest image)
PoC changes
No seeded PoC existed; wrote a negative-result run.sh (codec attach + /dev/dsp playback battery) and the path-analysis verdict instead.
Verified recommended fix
Clamp dir/stream at both hdacc sinks (reject dir outside 0..1, stream outside 1..15) as in fix.diff; fix the DF-4115 feeder arithmetic in hdac.c:1934.
Verdict
hdacc_stream_intr (hdacc.c:708) and hdacc_stream_free (hdacc.c:668) index codec->streams[dir][stream] with the controller-derived HDA stream tag without any bounds check. The DF-4115 BSS-tag sign error in hdac.c:1934 produces negative tags on BSS>0 controllers (stored with dev!=NULL at hdac.c:1940-42), and any stream interrupt for such a slot dispatches through an out-of-bounds-read device_t β a wild indirect call (certain crash; fault-injection class, not a new privilege boundary since a malicious controller already owns DMA). Not reproduced live: this guest's QEMU ICH6 reports OSS 4, ISS 4, BSS 0 (runtime caps, findings/poc/DF-4115/caps.txt), making the BSS branch structurally unreachable; the in-bounds path was exercised live (320KB playback through hdacc0/hdaa0/pcm0, RC=0, no panic).
No comments yet.