# DF-4827 — sw_i2cs[] array-domain mismatch in dce_i2c_sw.c (OOB read → wild write)

**File:** `sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c`
**Lines:** 91-92 (guard), 536 (OOB index), 392 (wild write)
**Severity:** High · **Class:** CWE-129 improper array-index validation → CWE-787 out-of-bounds write

## The bug

`get_hw_supported_ddc_line()` validates the DDC line number against
`GPIO_DDC_LINE_COUNT` (== 8), but the array it feeds, `resource_pool.sw_i2cs[]`,
is sized `MAX_PIPES` (== 6). DDC lines 6 (VGA pad) and 7 (I2C pad) pass the
guard and index past the array into the scalar members of `struct resource_pool`:

```
sw_i2cs[6]  aliases { i2c_hw_buffer_in_use, 3B pad, pipe_count }
sw_i2cs[7]  aliases { underlay_pipe_index, stream_enc_count }
```

Both are read as `struct dce_i2c_sw *` and are **non-NULL garbage** on every
real pool (e.g. `0x0000000600000000` for pipe_count=6). The subsequent
`engine->ddc = ddc` (dce_i2c_sw.c:392) then performs a **wild kernel write**
to the garbage address.

The sibling HW path guards correctly (`if (line < pool->pipe_count)`,
dce_i2c_hw.c:365); only the SW path kept the OLD i2caux guard — whose arrays
were sized `GPIO_DDC_LINE_COUNT` (i2caux.h:67) and for which the bound was
correct. The old i2caux engine arrays are `[GPIO_DDC_LINE_COUNT]`, the new
DC arrays are `[MAX_PIPES]` — the guard was not updated with the domain.

`en` ∈ {6,7} and `hw_supported` come straight from the GPU's VBIOS tables
(bios_parser2.c:395-405 → dc_link_ddc.c:269-276 → hw_translate_dce80.c:190-196),
so the trigger is a crafted/mis-declared VBIOS (flashed card, passthrough ROM)
or any legitimate board that maps a connector's DDC to the VGA/I2C pad with
the HW-assist bit. Entry is any I2C transfer on that link: HPD/detect EDID
reads (automatic) or the DRM i2c bus (`amdgpu_dm_i2c_xfer`, amdgpu_dm.c:3983-4016).
Reached on **every** DM ASIC (dce_i2c.c:46-58 tries the SW engine first).

## Reproduce on the guest

```
./build.sh          # compiles the REAL /usr/src/sys .../dce_i2c_sw.c (-Werror) + probe stubs
./run.sh            # phase 1: kldload -> PANIC; phase 2: patched -> clean NULL
```

**Expected (baseline):**

```
swi2cs: OOB READ pool.sw_i2cs[6] == 0x0000000600000000 (pipe_count=6 << 32 | hw_buf_in_use=0)
swi2cs: FIRING - calling the REAL dce_i2c_acquire_i2c_sw_engine(&pool, &ddc)...
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x600000000
fault code             = supervisor write data, page not present
Stopped at  dce_i2c_acquire_i2c_sw_engine+0x59:  movq %rbx,(%r12)   <-- engine->ddc = ddc
```

**Expected (fix.diff applied):** `swi2cs: acquire returned 0 (NULL = correctly
rejected)` — no panic, module loads and unloads cleanly.

The only stubbed layer is the GPIO service (`dal_ddc_open` etc.); its
emulated success for en=6 is documented from source in VERDICT.md §3.
