# DF-4337 VERDICT

**Status: inconclusive (kernel runtime hardware-gated; logic-level OOB
reproduced on the audit guest). Defect: certain.**

## Root cause (code-proven)

`struct atom_context` has no bios length member (atom.h:125-143). The bios
buffer is allocated to the *actual image size* — which is self-declared:
`radeon_read_platform_bios` uses `bios[2]*512` (radeon_bios.c:146-157,
accepts 512-byte images), `radeon_acpi_vfct_bios` uses `vhdr->ImageLength`
(radeon_bios.c:717), `radeon_read_bios` uses the vga_pci ROM size
(radeon_bios.c:98). Meanwhile every ATOM dereference uses unvalidated 16-bit
offsets from image content:

| Site | Expression |
|---|---|
| atom.c:1307-1309 | `cmd_table/data_table = CU16(base+0x1E/0x20)`, IIO ptr, msg ptr deref'd |
| atom.c:1366-1385 | `idx = CU16(data_table+offset)`, `*size=CU16(idx)`, `*data_start=idx` unvalidated |
| atom.c:1387-1402 | same for the command master table |
| atom.c:1165/1193 | `base = CU16(cmd_table+4+2*index)`, then `op = CU8(ptr++)` in an unbounded fetch loop |
| atom.c:279 | `val = U32(idx + gctx->data_block)` — up to 0x1FFFE (~128KB) |
| atom.c:633, 848 | calltable/setdatablock master-entry reads |
| atom.c:1334-1341, 1411-1418 | asic_init FWI chain, VRAM_UsageByFirmware struct |

With a 512-byte allocation, an offset of 0x8000 reads 32KB past the heap
object. The fetch loop is the worst instance: ATOM jumps
(`atom_op_jump`: `*ptr = ctx->start + target`, atom.c:749) and the
`atom_op_switch` case walk (atom.c:983-1001) move `ptr` with no upper bound,
so the interpreter keeps fetching "opcodes" from adjacent kernel heap until
an EOT byte (or an unmapped page → kernel page fault → panic).

## Reproduction

Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (env.txt), **no ATI/AMD GPU**
(`pciconf -l` shows only QEMU `0x1234:1111`), radeon/amdgpu not loaded — the
kernel path is unreachable on this guest (QEMU cannot emulate an ATI card
with a custom VBIOS ROM).

Logic-level PoC (run ON the guest): `atom_harness.c` copies the exact
expressions (`atom_parse_data_header` verbatim, atom.c:1366-1385) into a
userland mirror whose bios buffer ends at a 16MB `PROT_NONE` guard region.

Result (`run_cases.log`): `data_header` case — 512-byte image, master entry
0x8000 → **Segmentation fault, exit 139**: the `*size = CU16(idx)` read at
atom.c:1378 lands 32KB past the allocation. Identical expression, identical
index arithmetic, identical result class in-kernel (heap OOB read).

## Fix validation (logic-level)

`fix.diff` adds `bios_size` plumbing + `atom_in_bios()` gates at every site
above (incl. the fetch loop). Validated:
1. `git apply --check` passes against the pristine tree (all 5 files).
2. `atom_harness_fixed.c` re-runs the same cases with the *fixed* logic on
   the guest: `data_header` → `ok=0` (entry rejected vs bios_size=512,
   exit 0, no trap) — see fix_run.log.

A kernel-build + on-hardware validation was not possible: the guest has no
ATOM hardware to attach the patched driver to (hence fix_status
`inconclusive`, not `fixed`).

## Downstream neutralization assessment (the central-fix question)

Porting this fix to the amdgpu twin (`amd/amdgpu/atom.c:1296-1419`, same
verbatim functions `amdgpu_atom_parse_data_header/cmd_header`) plus
`amdgpu_bios.c`/`amdgpu_atombios.c:2037` plumbing:

- **DF-4104** (atombios_encoders LVDS_Info fixed 0x2C reads at unvalidated
  data_offset): **neutralized** — parse_data_header then guarantees
  `data_offset + size <= bios_size` and `size >= 4`; recommend the port also
  enforce `size >= 0x2C` for that caller (or a caller-side check).
- **DF-4152** (amdgpu_atombios object-table walks): **partially** — the
  outer data-table offsets become validated; the *inner* walks (object
  header → path arrays with their own strides/counts) still need per-site
  `bios_size` bounds.
- **DF-4289** (ppatomctrl SS walk bounded by its own usStructureSize):
  **partially** — the table entry/offset becomes validated; the walk itself
  must be bounded by bios end (per-site fix still required).
- **DF-4168** (bios_parser2.c DC parser bounds by `bios[2]*512`):
  **not neutralized** — bios_parser2 never calls atom_parse_data_header; it
  needs its own image-length fix.
- **DF-4048** (command_table ulSymClock truncation): **not affected** — not
  a bounds defect.

radeon-side callers (radeon_atombios.c, atombios_encoders.c, ci/ni/rv770/
kv/rs* _dpm.c — ~40 `atom_parse_data_header/cmd_header` call sites) get the
same outer-offset guarantee from this diff alone.

## Bottom line

Certain, unbounded OOB-read primitive (up to 128KB reach on the ID path,
unbounded on the fetch-loop path) at driver attach, enabler of the whole
DF-4104/4152/4289 family. Kernel-runtime escalation was not demonstrable on
the audit guest because the trigger hardware does not exist there; the
primitive itself is proven at the expression level on the guest, and the fix
is validated to close it.

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