# DF-4168 — DC bounds all VBIOS walks by the image's self-declared size byte (`bios[2]*512`), not the allocated buffer length

- File: `sys/dev/drm/amd/display/dc/bios/bios_parser2.c:1930` (`bios_parser_construct`: `bios_size = bios[2] * 512`)
- Bound consumer: `sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:36-44` (`bios_get_image`: the only bounds check, `offset + size < bp->bios_size`)
- Allocation side (never plumbed into DC): `sys/dev/drm/amd/amdgpu/amdgpu_bios.c:123-150` (`amdgpu_read_bios`: `kzalloc(size)` with the `pci_map_rom` size; `check_atom_bios` (:50-81) validates magic + ATOM pointer only, never cross-checks `bios[2]*512`), `igp_read_bios_from_vram` (256KB), `amdgpu_read_bios_from_rom` (`AMD_VBIOS_LENGTH`), ATRM (256KB)
- Class: heap out-of-bounds READ, CWE-125. Hardware-gated (AMD ASIC + crafted VBIOS image whose size byte exceeds the true allocation — small ROM BARs, vfio/QEMU romfile sizing, or a malicious device).

## Verified via the verbatim-code rig (`rig/`)

The production construction + table-walk code was sed-extracted byte-identical
from `sys/` (`gen_rig.sh`), re-verified **on the DragonFly guest against
`/usr/src/sys`** (`check_verbatim.sh`: 30 blocks `identical to production` +
the fix-variant equivalence proofs), compiled on the guest, and driven with a
crafted image: a fully valid ATOM ROM (header v2.2 @0x40, master data table
v2_1 @0x20, displayobjectinfo v1_4 @0x60 — everything `bios_parser_construct`
needs, all **inside** the 4096-byte "allocation") whose `bios[2] = 0xFF`
claims **130,560 bytes** — 31x the allocation.

## Build / run (on the DragonFly guest)

```
cd rig && sh ./build.sh /usr/src
cd rig && sh ./run.sh
```

## Modes

`layout`, `size-mismatch` (the claim itself), `oob-guard` (allocation ends at
a PROT_NONE page; firmwareinfo/dce_info table offsets point past the
allocation but inside the claimed size), `oob-canary` (readable "adjacent
heap" region; tables crafted there with `dce_refclk_10khz = 0x1234`),
`guard-construct` (the ROM-header pointer at image[0x48] points past the
allocation — construct's OWN walk).

## Expected output (see run.log / run.2.log)

Baseline (production code):
- `size-mismatch`: construct **SUCCEEDS**; `bp->base.bios_size = 130560`
  vs true allocation 4096 — every GET_IMAGE walk may read up to offset
  130560 (ceiling ≈ 126 KB past this allocation)
- `oob-guard`: `SIGSEGV` **reading** at mapping offset 4354 (= the
  firmwareinfo header byte at 0x1100+2), 258 bytes past the 4096-byte
  allocation, while the claimed bound (130560) happily admits the offset →
  **OOB READ past the heap allocation**
- `oob-canary`: `BP_RESULT_OK` with `crystal_frequency = 46600` — exactly
  `0x1234 × 10` read from offset 0x1300, **768 bytes past the allocation**:
  adjacent-region bytes parsed into DC state (earlier run with plain 0x41
  filler parsed 0x4141 → 167050 — whatever sits past the allocation becomes
  the reference clock)
- `guard-construct`: `SIGSEGV` reading at offset 9474 (0x2500+2) —
  construct's own `GET_IMAGE(atom_rom_header_v2_2, *0x48)` walk
  (bios_parser2.c:1942) past the allocation

Fixed (`-DAPPLY_DF4168_FIX` = the bios_parser2.c + bios_parser_interface.h
hunks of fix.diff, checker-proven; the harness plumbs `init.bios_size =
4096`, the role of fix.diff's amdgpu_dm.c/dc.c plumbing):
- `size-mismatch`: `bios_size` **clamped to 4096**
- `oob-guard` / `oob-canary`: construct succeeds, the firmware-info walk
  returns `BP_RESULT_BADBIOSTABLE`, sentinel info struct untouched, **no
  fault, no adjacent-region read** on the identical image
- `guard-construct`: construct rejects cleanly (`GET_IMAGE` → NULL), no
  fault

## Verdict

Reproduced (verbatim-rig evidence class): the exact production code bounds
every VBIOS walk by the attacker-supplied size byte and reads past the true
heap allocation — guard-page fault proof plus adjacent-region canary proof.
fix.diff (plumb the allocated length and clamp) eliminates it — A/B
validated in the same rig. See `VERDICT.md`, `verdict.json`.
