# DF-4636 VERDICT — radeon_acpi ATIF/ATCS parser type-confusion / OOB read

**Status: reproduced (verbatim-extraction control-flow proof on the guest)**
**Impact: dos (firmware-controlled kernel wild-pointer/OOB read → panic)**
**Confidence: certain**

## 1. Root cause (source-faithful)

`sys/dev/drm/radeon/radeon_acpi.c` has four parsers of ACPI method return
buffers — `radeon_atif_verify_interface` (:198-231),
`radeon_atif_get_notification_params` (:245-294),
`radeon_atif_get_sbios_requests` (:307-334),
`radeon_atcs_verify_interface` (:480-512). Each does, with zero validation of
the returned `ACPI_OBJECT`:

```c
size = *(u16 *) info->Buffer.Pointer;   /* :212, :259, :318, :494 */
...
size = min(sizeof(output), size);
memcpy(&output, info->Buffer.Pointer, size);   /* :220, :267, :326, :502 */
```

`radeon_atif_call`/`radeon_atcs_call` (:103-138, :414-449) return
`buffer.Pointer` whenever `AcpiEvaluateObject` does not fail. ACPICA
(`nsxfeval.c:536-599`) externalizes *any* return object type on AE_OK — an
SBIOS ATIF/ATCS method that returns an Integer, a zero-length Buffer, or a
Buffer whose in-band size header exceeds the allocation reaches the parsers
unchoked. In `union acpi_object` (actypes.h:1077-1098) `Buffer.Pointer` and
`Integer.Value` share offset 8, so a returned INTEGER makes the parser
dereference the firmware-chosen 64-bit value as a kernel pointer. This is the
radeon twin of pass-1 amdgpu finding DF-1750 (same defect, different file —
this file was clean in pass 1).

Threat model: the input is platform firmware, not an unprivileged user. A
malicious or buggy SBIOS (or crafted ACPI tables, which require root/loader
control to install) gets a kernel-mode read at a chosen 64-bit address, or a
1..13-byte heap OOB read past an M_ACPICA allocation; any fault lands in ACPICA
notify-dispatch or driver-init context → kernel panic. Parsed output feeds
internal notification config masks and `radeon_set_backlight_level()`
(atombios arithmetic only, atombios_encoders.c:87-112 — no indexing), so there
is no escalation sink; the impact ceiling is DoS. Severity Medium (matches
DF-1750's calibration for the identical amdgpu defect).

## 2. Proof (what was run, what was seen)

Methodology identical to accepted DF-4605: verbatim extraction + faithful shim.

- Guest `/usr/src/sys/dev/drm/radeon/radeon_acpi.c` sha256
  `794cb6c29cb9e9fd17d4c826e86389ecb44a778e1f80ace15f7cc3e1a3410cb1` —
  byte-identical to the audited tree (script aborts otherwise); same for
  `radeon_acpi.h`. The four parsers + 5 helpers are awk-extracted verbatim
  (258 lines) and compiled on the guest (cc 8.3) against an actypes.h-faithful
  `union acpi_object` (aliasing confirmed at runtime:
  `offsetof(Integer.Value)=8 offsetof(Buffer.Pointer)=8`) with a mock
  `AcpiEvaluateObject` that returns AE_OK with firmware-chosen objects,
  exactly as ACPICA does after externalization.

Result (run.log, 4 rounds, 5 fault fixtures × 4 iterations each + control):

| fixture | expected fault | observed |
|---|---|---|
| ATIF returns INTEGER 0x414141414100 | `*(u16*)0x414141414100` | 16× `SIGSEGV si_addr=0x414141414100` |
| ATCS returns INTEGER 0x424242424200 | same shape | 16× `SIGSEGV si_addr=0x424242424200` |
| ATIF returns Buffer{len 0, NULL} | `*(u16*)NULL` | 16× `SIGSEGV si_addr=0x0` |
| ATIF returns 1-byte buffer | u16 header read +1 OOB | 16× `SIGSEGV si_addr=0x80047d000` (guard page = buffer_end+1) |
| ATIF returns 4-byte buffer claiming 12 | memcpy 12 from 4 | 16× `SIGSEGV si_addr=0x80047d000` (guard page = buffer_end+4..11) |
| control: well-formed 13-byte buffer | parses, err=0 | 4×/round `exit=0`, no fault |

`PROOF-RESULT: BASELINE-CORRUPTS` in 4/4 full rounds. Every fault address is
exactly the firmware-supplied value / exactly one guard page past the ACPI
allocation — deterministic, no heuristics.

Iterations fixed during verification (all mine, not the kernel's):
1. sha256 typo in the gate (my transcription),
2. extraction bugs: indented `#  define` lines in radeon_acpi.h missed by the
   grep; a line-range sed of radeon.h swallowed a trailing `/*` — both replaced
   by pattern-bounded extraction,
3. first wild-address values (0x4141414100000000) were non-canonical x86-64
   addresses → #GP reports si_addr=0x0; switched to canonical values so si_addr
   prints the dereferenced pointer verbatim.

## 3. Fix validation

`fix.diff` (git-apply --check clean against the unmodified sys/ tree): after
each `if (!info)` the parsers now require `info->Type == ACPI_TYPE_BUFFER`,
`info->Buffer.Pointer != NULL`, `info->Buffer.Length >= 2`, and each parse is
bounded by `info->Buffer.Length >= size` before any memcpy. The same harness
compiled from the fix.diff-patched extraction returns `-EINVAL` for all five
malformed fixtures with **no fault** in 4/4 rounds, while the well-formed
control still parses (`PROOF-RESULT: FIXED-CLEAN` ×4 in run.log).

Kernel-level build/run validation is not possible on this guest (no AMD GPU;
radeon absent from the guest kernel config and module set), so
fix_status is `inconclusive` with the harness-level validation as evidence —
same posture as DF-4605.

## 4. Why this is a real finding and not noise

- The identical defect in the sibling amdgpu driver is an accepted pass-1
  finding (DF-1750); this file was audited clean in pass 1 and the pass-2
  deeper look found the radeon twin.
- ACPICA externalizes non-BUFFER returns on success (nsxfeval.c:536-599 read
  line-by-line during this verification) — the "firmware will always return a
  buffer" assumption is unenforced by anything in the path.
- Every code path is firmware-data-driven with zero validation before the first
  dereference; the control fixture proves the extraction reproduces the real
  parser semantics (well-formed input parses to err=0).

Guest stayed up throughout (userland harness only); no reset required.

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