# DF-4828 — r600_hdmi.c consumes lvds-typed enc_priv as atom_dig on the audio-work path (OOB read at dig->afmt +288 → wild deref + write-through)

**File:** `sys/dev/drm/radeon/r600_hdmi.c`
**Lines:** 283-291 (`r600_hdmi_buffer_status_changed`), 425-435
(`r600_hdmi_update_audio_settings`), 271-274 (`r600_hdmi_is_audio_buffer_filled`)
**Severity:** High · **Class:** CWE-843 type confusion → CWE-125/CWE-787
(Same root-cause family as DF-4635 (radeon_audio.c) — **new sinks, new
trigger path**; not the DF-1911 encoder_list race, no race required.)

## The bug

`r600_audio_update_hdmi()` (the audio interrupt work, scheduled from the IH
handlers r600.c:4341 / evergreen.c:4900 / rs600.c:821) walks every encoder
where `radeon_encoder_is_digital()` is true — which includes every
LCD-carrying digital encoder (KLDSCP_LVTMA/UNIPHY*/LVTM1/TMDS1 with
`ATOM_DEVICE_LCD_SUPPORT`; radeon_encoders.c:423-443). For exactly those
encoders, `enc_priv` is a **280-byte `radeon_encoder_lvds`**
(atombios_encoders.c:2860-2872), but r600_hdmi.c consumes it as a
**312-byte `radeon_encoder_atom_dig`**:

```
r600_hdmi.c:283   dig = radeon_encoder->enc_priv;         /* lvds* ! */
r600_hdmi.c:286   if (!dig->afmt || !dig->afmt->enabled)    /* dig->afmt = +288 = OOB READ */
r600_hdmi.c:291   dig->afmt->last_buffer_filled_status = status;  /* WRITE through garbage ptr */
r600_hdmi.c:433   same OOB read + deref in update_audio_settings
r600_hdmi.c:272   dig->afmt->offset  -> feeds WREG32 MMIO offsets
```

DragonFly's slab rounds 280 → **288-byte chunk with zero slack**
(kern_slaballoc.c zoneindex, 32-byte granularity in 256..512), so `+288`
reads the **first 8 bytes of the adjacent kernel object** — groomable heap
content becomes a wild `radeon_afmt *` that the driver dereferences
(`->enabled`, `->offset` → MMIO) and writes through
(`->last_buffer_filled_status`).

Trigger: any audio interrupt status change on r600/evergreen-era hardware
with an LCD-carrying digital encoder present (every LVDS/eDP laptop of the
family) — **no modeset, no race, no user interaction** beyond audio
activity (DF-4635's sinks needed a connector probe; these fire from the
interrupt work directly).

## Reproduce on the guest

```
./build.sh          # builds r600_hdmi_offsets (userland) + afmt_walk.ko (KLD)
./fixbuild.sh       # optional: full radeon.ko rebuild with fix.diff (-Werror)
```

**Expected (KLD, live kernel allocator):**

```
sizeof(radeon_encoder_lvds)     = 280
sizeof(radeon_encoder_atom_dig) = 312
offsetof(dig, afmt)             = 288
afmt_walk: 8192 objs of 280 bytes; usable chunk = 288 (want 288)
afmt_walk: adjacent pair A=... B=... (B-A=288)
afmt_walk: OOB READ dig->afmt (+288) == 0xffffffff82600380 (non-NULL -> guard ... falls through)
afmt_walk: after call#1(status=1): last_buffer_filled_status=1  <- WILD WRITE via OOB-read pointer (r600_hdmi.c:291)
afmt_walk: update_audio_settings replica (r600_hdmi.c:433->435): ... dig->afmt->offset == 0x1234 -> would feed WREG32(HDMI0_* + offset) MMIO
```

(The driver path itself is hardware-gated on this guest — no radeon GPU,
no /dev/dri — so the KLD reproduces the exact heap geometry and the exact
audited accesses; see VERDICT.md.)
