# DF-4168 — VERDICT (verify run, 2026-10-15)

**Status: REPRODUCED (verbatim-rig evidence class). The production
construction + table-walk code, byte-identical to the running kernel's
source, walks past the true heap allocation whenever the image's self-
declared size byte exceeds it: guard-page SIGSEGV reads at offsets the
claimed bound admits, and adjacent-region bytes are parsed into DC state.
In-kernel execution is AMD-ASIC-gated on this guest (no AMD GPU — proven,
see env.txt). Fix A/B validated in the same rig: fixed.**

## 1. What was verified and how

Same evidence class as DF-4165/DF-4167 (siblings on this exact file): the
guest cannot run amdgpu/dc (Bochs SVGA 0x1234:1111 only, `sysctl dev.drm`
unknown, no drm/amdgpu KLDs — `env.txt`), so the **actual production code**
was compiled into a guest-side harness:

- `rig/gen_rig.sh` sed-extracts, byte-identical from the read-only `sys/`
  tree: `bios_get_image` (bios_parser_helper.c:36-44 — the only bounds
  check on the path), `get_atom_data_table_revision`
  (bios_parser2.c:133-151), `bios_parser_get_firmware_info` (:1119-1155)
  → `get_firmware_info_v3_1` (:1157-1202) / `get_firmware_info_v3_2`
  (:1204-1280) as the demonstration walk, **`bios_parser_construct`
  (:1912-1992, the `bios_size = bios[2] * 512` line at :1930)** and
  `firmware_parser_create` (:1994-2009) — plus every struct they touch
  (atomfirmware.h tables verbatim under the production `#pragma pack(1)`
  at :207/:2934, rom header v2_2, master data table v2_1,
  displayobjectinfo v1_4, bp_init_data, dc_firmware_info).
- `rig/check_verbatim.sh` re-proved all 30 blocks **byte-identical**, run
  both against the host `sys/` tree and **on the guest against
  `/usr/src/sys`, the tree its running kernel (Jul 2 2026 build) was
  compiled from**: `ALL VERBATIM CHECKS PASSED` (build.log). The checker
  additionally **applies the bios_parser2.c hunk of fix.diff to the
  production construct** and proves the `APPLY_DF4168_FIX` variant equals
  the patch result, and that the `bp_init_data.bios_size` member insert
  equals the bios_parser_interface.h hunk's additions. (The dc_types.h /
  amdgpu_dm.c / dc.c hunks are caller-side plumbing; the harness plays
  their role by filling `init.bios_size` with the true allocation length.)
- Harness shims touch nothing on the bounds path: minimized
  `dc_bios`/`bios_parser` (first-member layout, container_of identity),
  kzalloc→placement, `vbios_funcs` macro-mapped to a shim table whose
  single member dispatches to the verbatim `bios_parser_get_firmware_info`
  (so construct's `bp->base.funcs = &vbios_funcs` is a real dispatch);
  `dal_firmware_parser_init_cmd_tbl` /
  `dal_bios_parser_init_cmd_tbl_helper2` (command-table pointer init —
  never touch bios_size or image bytes) and
  `bios_parser_create_integrated_info` (the integratedsysteminfo walk
  verified separately as DF-4165; returns NULL, which construct tolerates)
  are no-op shims, documented.

The crafted image is a fully valid minimal ATOM ROM — header v2_2 @0x40
(revision 2.2), master data table v2_1 @0x20 (firmwareinfo → 0x100 or
0x1100, dce_info → 0x200 or 0x1300, displayobjectinfo → 0x60, revision
1.4) — everything `bios_parser_construct` requires, all inside the
4096-byte allocation, with `bios[2] = 0xFF`.

## 2. Observed (run.log, stable across run.2.log)

- **size-mismatch** (the core claim): `firmware_parser_create` →
  `bios_parser_construct` **SUCCEEDS**; `bp->base.bios_size = 130560`
  (from the image byte) vs true allocation 4096 — a 31x over-trust. Every
  subsequent `GET_IMAGE` / table walk is bounded by the claimed value:
  any offset < 130560 − sizeof(table) is admitted. Ceiling with bios[2]
  = 0xFF: reads up to 126 KB past this allocation (the finding's ~95 KB
  figure corresponds to a 32 KB true allocation; same mechanism).
- **oob-guard**: allocation [0,4096) ends at a PROT_NONE page;
  firmwareinfo @0x1100, dce_info @0x1300 (both past the allocation,
  inside the claimed 130560). Construct succeeds; the firmware-info walk
  (`bios_parser_get_firmware_info` → `GET_IMAGE(header, 0x1100)` passes
  `0x1100+4 < 130560` → `get_atom_data_table_revision` dereferences) →
  **`SIGSEGV` READING at mapping offset 4354 = 0x1102** — 258 bytes past
  the 4096-byte allocation: **OOB heap read in the production walk**
  (rc=88 from the handler). In-kernel this is a read past the
  `adev->bios` kzalloc.
- **oob-canary**: the region past the allocation made readable and
  pre-filled 0x41, with the tables crafted there
  (`dce_refclk_10khz = 0x1234` @0x1300): the walk returns
  **`BP_RESULT_OK` with `crystal_frequency = 46600` = 0x1234 × 10 read
  from 768 bytes past the allocation** — adjacent-heap bytes parsed into
  DC's reference-clock state. (An earlier run with plain 0x41 filler
  parsed `0x4141 → 167050`: whatever bytes follow the allocation become
  firmware "data".)
- **guard-construct**: image[0x48] (the ROM-header pointer) = 0x2500:
  construct's own `GET_IMAGE(struct atom_rom_header_v2_2, *0x48)` passes
  (`0x2500+40 < 130560`) and `get_atom_data_table_revision(&rom_header-
  >table_header,…)` (:1947) dereferences → **`SIGSEGV` READING at offset
  9474 = 0x2502**, past the allocation — construct itself walks OOB
  (bios_parser2.c:1942), not only the consumers.

Allocation-side facts confirmed by source (no rig needed):
`amdgpu_read_bios` allocates `kzalloc(size)` with the `pci_map_rom` size
(amdgpu_bios.c:130-141); `check_atom_bios` (:50-81) validates the 0x55AA
magic and the ATOM signature only — it never compares `bios[2]*512`
against `size`; `struct bp_init_data` (bios_parser_interface.h:33-36)
carries only the pointer, so the allocation length is never plumbed into
DC — exactly the gap fix.diff closes.

## 3. Primitive and impact

Primitive: **attacker-bounded OOB kernel heap reads** — with a crafted
image claiming 0xFF units, every table/record walk in bios_parser2 (and
any DC consumer of `bp->bios_size`) may read up to 130,560 bytes past the
base of a heap allocation that can be as small as the true ROM image
(small ROM BARs; vfio/QEMU romfile where the BAR is sized independently of
the image-size byte). Demonstrated: guard-page fault (crash-class when the
past-end memory is unmapped) and silent adjacent-heap disclosure into
driver-parsed state (reference clock, engine/memory clocks, table
revisions). Gating: AMD ASIC presenting the crafted/oversized-claim ROM —
the finding's threat model (flashed ROM, vfio-pci `romfile=`, malicious
device); no unprivileged userland trigger.

Impact recorded as `leak` (CWE-125 OOB read; adjacent kernel heap contents
are consumed as parser data). The crash flavor is also demonstrated
(oob-guard, guard-construct) but reads are the primary primitive. No write
primitive arises from this finding; DF-4165 covers the write side of this
parser.

## 4. Fix validation (A/B, identical crafted image)

`rig_fixed` = same verbatim slice + exactly the bios_parser2.c clamp and
the bp_init_data member of fix.diff (checker-proven), with the harness
providing `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: construct succeeds (in-allocation tables pass the clamped
  bound), the firmware-info walk returns `BP_RESULT_BADBIOSTABLE`
  (GET_IMAGE NULL → revision 0 → no dispatch), sentinel info struct
  untouched, **no fault**;
- oob-canary: `BP_RESULT_BADBIOSTABLE`, "info untouched (sentinel
  intact: yes), no table bytes parsed" — **no adjacent-region read**;
- guard-construct: `GET_IMAGE` rejects 0x2500 → construct returns false
  cleanly, **no fault**.

Baseline reads past the allocation, patched slice rejects out-of-allocation
tables on the identical image → **fix validated** (`fix_validation.log`).
Kernel-rebuild validation not performed (fix site executes in-kernel only
on AMD ASICs absent from this guest; per the verbatim-rig protocol the
fixed slice was A/B-validated in the same rig). `fix.diff` remains authored
against the pristine read-only `sys/` tree and was never applied there.

## 5. Honest limitations

- Demonstrated with production instructions and production struct layouts,
  but in a userland mapping standing in for the kernel heap; the
  allocation/claimed mismatch itself (small ROM BAR, vfio romfile sizing)
  is established by source reading (amdgpu_bios.c) plus the QEMU sizing
  argument, not by presenting a real undersized BAR to this guest.
- The walk demonstrated is firmwareinfo/dce_info and construct's rom-header
  read; the same claimed-size bound governs every other table walk in the
  file (object tables, encoder caps, board layout — and DF-4165's
  integratedsysteminfo), but each was not separately re-driven.
- The exfiltration path of the canary variant lands in DC-internal state
  (clocks parsed from adjacent heap); no direct copy-to-userspace of the
  OOB bytes was demonstrated — hence impact `leak` at the
  parsed-state-corruption/potential-information-influence level rather
  than a demonstrated userland info leak.

## 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.
