# DF-4636 — radeon_acpi: ACPI_OBJECT type confusion + missing Buffer.Length validation in all 4 ATIF/ATCS return parsers

**File:** `sys/dev/drm/radeon/radeon_acpi.c` (pass-2 finding)
**Verification:** verbatim-extraction control-flow proof on the DragonFly QEMU guest
(methodology identical to accepted finding DF-4605) — **BASELINE-CORRUPTS 4/4
rounds, FIXED-CLEAN 4/4 rounds.**

## What the bug is

All four ATIF/ATCS return-buffer parsers in `radeon_acpi.c`:

| parser | u16 size read | memcpy |
|---|---|---|
| `radeon_atif_verify_interface` | radeon_acpi.c:212 | radeon_acpi.c:220 |
| `radeon_atif_get_notification_params` | radeon_acpi.c:259 | radeon_acpi.c:267 |
| `radeon_atif_get_sbios_requests` | radeon_acpi.c:318 | radeon_acpi.c:326 |
| `radeon_atcs_verify_interface` | radeon_acpi.c:494 | radeon_acpi.c:502 |

read `size = *(u16 *) info->Buffer.Pointer;` and then `memcpy` out of
`info->Buffer.Pointer` **without ever checking**:

1. `info->Type == ACPI_TYPE_BUFFER` — if the SBIOS method returns an
   ACPI_TYPE_INTEGER (perfectly legal for ACPICA — `AcpiEvaluateObject`
   externalizes any object type on AE_OK), `info->Buffer.Pointer` is a union
   alias of the firmware-chosen 64-bit `Integer.Value` → **wild-pointer kernel
   read at a firmware-chosen address** (verified: si_addr is the integer,
   verbatim).
2. `info->Buffer.Pointer != NULL` — a 0-length buffer → NULL+0 fault.
3. `info->Buffer.Length >= 2` before the u16 size read → 1-byte OOB read past
   the ACPICA allocation.
4. `info->Buffer.Length >= size` before the memcpy → up to
   `sizeof(struct atif/atcs_*output)` (8–13) bytes heap OOB read past the
   ACPICA allocation when the buffer's own size header lies.

Trigger: any SBIOS/firmware that implements ATIF/ATCS with a malformed return
(bug or malice). Parsed values feed internal notification config and
`radeon_set_backlight_level()` (arithmetic only — no indexing), so the impact
ceiling is kernel-mode wild/OOB read → panic (DoS), not escalation.

## How to reproduce

```
scp -F dfbsd-qemu/config extract_and_test.sh fix.diff dfbsd:/tmp/df4636_dir/
./dfbsd-qemu/vm.sh run_root 'sh /tmp/df4636_dir/extract_and_test.sh'
```

The script (single-tenant guest must be up: `dfbsd-qemu/vm.sh status`):

1. sha256-gates the guest's `/usr/src/sys/dev/drm/radeon/radeon_acpi.c` and
   `.h` against the audited tree (byte-identical),
2. awk-extracts the four parsers **verbatim** (plus `*_call` and the
   `*_parse_*` helpers),
3. compiles them against a faithful ACPICA shim (union layout printed at
   runtime: `offsetof(Integer.Value)==offsetof(Buffer.Pointer)==8`) with a
   mock `AcpiEvaluateObject` returning firmware-chosen objects,
4. drives 5 fault fixtures + 1 well-formed control, 4 iterations each, in
   forked children with a SIGSEGV tracer printing exact `si_addr`,
5. re-extracts from a copy patched with `fix.diff` and repeats everything.

## Expected output (decisive)

```
PROOF-RESULT: BASELINE-CORRUPTS (5/5 fixtures SIGSEGV 4/4 iterations, control parses)
PROOF-RESULT: FIXED-CLEAN (no faults, control still parses)
```

with fault-address distribution over 4 rounds (see `run.log`):

| fixture | si_addr | meaning |
|---|---|---|
| integer_wild_atif | 0x414141414100 (16×) | SBIOS integer value dereferenced verbatim |
| integer_wild_atcs | 0x424242424200 (16×) | same, ATCS parser |
| null_buffer | 0x0 (16×) | NULL+0 fault |
| shortbuf_hdr | guard page (16×) | u16 size read 1 byte past 1-byte ACPI buffer |
| lying_size | guard page (16×) | memcpy reads 8 bytes past 4-byte ACPI buffer |
| control_wellformed | no fault, err=0 | parser logic intact (both variants) |

## Honest scope caveat

The in-kernel end-to-end path could not run on this guest: it has no AMD GPU
(virtio-vga) and radeon is not built into any guest kernel config. The proof
is at the verbatim-extraction level against the byte-identical shipped source
(deterministic, source-faithful). In the kernel, every fault observed here is
a kernel-mode fault in ACPICA notify/init context → panic.

## Fix

`fix.diff` (git-apply --check clean against `sys/`): add a Type/NULL/Length>=2
guard after each `if (!info)` and require `info->Buffer.Length >= size` before
each memcpy/parse. Validated in the same harness: FIXED variant rejects all
malformed fixtures with `-EINVAL` while the well-formed control still parses.
