# VERDICT — DF-4605

**Bottom line:** CONFIRMED (certain) by verbatim-source control-flow
reproduction on the DragonFly guest: the port-time `#if 0` gutting of the
event guard in `amdgpu_atif_handler` leaves an empty `if` body, so control
falls through to `atif->functions.sbios_requests` — a load at `NULL+0x12`
whenever `adev->atif == NULL`, which is the steady state on every ATIF-less
amdgpu system while the ACPI notify handler is installed unconditionally
(amdgpu_acpi.c:808-812). Kernel impact: panic (DoS) on the first firmware
ACPI_DEVICE_NOTIFY on the GPU node. The proposed one-line fix (restore the
early `return;`) eliminates the fault in the same harness.

## 1. What was executed on the guest

Guest: DragonFly 6.5-DEVELOPMENT x86_64 (#0 Thu Jul 2 06:02:54 UTC 2026),
QEMU/KVM, cc 8.3 [DragonFly]. `env.txt` records that the guest has **no AMD
GPU** (only virtio-vga 0x1111:1234) and **amdgpu is not built** into the
kernel or module set (`sys/conf/files` has no amdgpu entries; no
/boot/modules). Therefore the *in-kernel* end-to-end path
(firmware notify → AcpiEvNotifyDispatch → amdgpu_acpi_event → panic) **cannot
be executed on this guest** — that is a hardware limitation, not a doubt
about the code path.

What CAN be executed faithfully is the control flow itself:

- `extract_and_test.sh` extracts lines 374–385 (guard) and line 387 (deref)
  with `sed` from `/usr/src/sys/dev/drm/amd/amdgpu/amdgpu_acpi.c` on the
  guest — the exact bytes of the shipped kernel source;
- compiles them inside a wrapper using the byte-exact `struct amdgpu_atif`
  layout from amdgpu_acpi.c:38-75 (verified at runtime:
  `sizeof(struct amdgpu_atif)=40`, `offsetof(functions.sbios_requests)=18`);
- calls the function with a runtime-produced NULL `atif` and a SIGSEGV
  handler that prints the faulting address.

Result (run.log, run.stress.log — 4/4 deterministic):

```
RESULT: SIGSEGV at address 0x12
VERDICT: guard body compiled to NOTHING; control fell through the if() into the NULL deref
```

The fault address **0x12 (18)** matches `&((struct amdgpu_atif *)NULL)->functions.sbios_requests`
exactly. The fixed variant (identical extraction with the `#if 0` block
replaced by `return;`) returns cleanly, 4/4.

## 2. PoC debugging notes (why earlier iterations "passed")

Two harness iterations initially showed no fault, both instructive and
**neither a refutation**:

1. *Literal NULL argument* (and later a `static ... * volatile` initialized to
   NULL): gcc 8.3 constant-propagated the provable NULL through the `noinline`
   static function and deleted the call as UB dead code (verified in the
   disassembly: main loaded the volatile into `%rax` and never called).
   In the kernel this is impossible: `atif = adev->atif` is an opaque heap
   field read at notify-dispatch time inside ACPICA — not foldable.
   Fixed by deriving the NULL from `getenv()` at runtime.
2. *Side-effect-free deref body* (`(void)type;`): gcc dropped the entire
   `if (atif->functions.sbios_requests)` statement because an empty-bodied if
   whose condition is a pure load has no observable effect — confirmed even
   at -O0 (function size 0x4a, no `0x12` displacement anywhere). The kernel
   body is NOT empty (amdgpu_acpi.c:388-431 calls
   `amdgpu_atif_get_sbios_requests()` and the backlight/HPD paths), so the
   load is emitted in any real build. Fixed by giving the harness body a
   `printf` side effect; the fault then appears deterministically.

These are properties of the *harness*, documented so nobody re-derives them
as a refutation. The final harness (both barriers in place) is
`generated_guard_test.c`.

## 3. Kernel-context reasoning (why 0x12 ⇒ panic, not a benign read)

- The read executes in ACPICA notify-dispatch context (kernel thread);
  virtual address 0x12 lies in user VA space whose page 0 is unmapped in the
  dispatching thread's pmap ⇒ kernel-mode page fault ⇒ DragonFly panics
  ("fault on non-existent ... / segmentation fault") — no SMAP/SMEP needed
  for this, it is a plain unmapped read.
- No privilege escalation surface: the faulting address is a fixed NULL+0x12
  with no attacker-controlled offset; the value read is never used before the
  trap. Pure DoS.

## 4. Realistic trigger population

- Any **discrete desktop AMD GPU** system (no ATIF method in DSDT — the
  normal desktop firmware state) + any firmware device-notify on the GPU's
  ACPI node: display hotplug notifications, dock events, PCIe hotplug/PM
  events, buggy-BIOS notify storms.
- Laptops where `amdgpu_atif_verify_interface` or the atif `kzalloc` fails:
  handler still installed (amdgpu_acpi.c:808-812), `adev->atif == NULL`.
- Note (functional, not security): on PX/hybrid laptops where ATIF lives in
  the iGPU namespace, the handler is installed on the dGPU node — events may
  simply never arrive there; the crash population is chiefly ATIF-less
  desktops, which is also where amdgpu is most common.

## 5. Fix validation

- Harness-level: fixed variant returns cleanly 4/4 (baseline faults 4/4).
- Kernel-build level: **not testable on this guest** — amdgpu is not part of
  any buildable kernel config here and no AMD GPU exists to probe;
  `fix.diff` is authored against the read-only `sys/` tree and applies
  cleanly (`git apply --check` on the host tree).

## 6. Relation to prior findings on this file

DF-1749 (stale handler after detach → freed-adev UAF), DF-1750 (4-parser
OOB/type-confusion), DF-1751 (allocator mismatch on atif). DF-4605 is
independent: live adev, NULL atif, first-notify crash, no teardown 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.
