# DF-4884 VERDICT — double-free of `adev->bios` when `amdgpu_get_bios()` fails

**Status: not_reproduced on this guest (target hardware absent) — bug
established by exhaustive source trace; every hop cited.**

## Reproduced?

No — and honestly so: the QEMU guest has **no AMD PCI device** (env.txt:
`pciconf -l | grep -c 0x1002` = 0; the only VGA is the QEMU std-VGA,
vendor 0x1234). `amdgpu.ko` is present but can never attach, so the trigger
condition (amdgpu driver probe) is unreachable on this guest. No amount of
guest setup short of PCI passthrough of real AMD hardware can reach the code.
This is a hardware/firmware-precondition bug (probe-time), not a
runtime-unprivileged one.

## The bug (full trace, every hop verified in this tree)

All six VBIOS read methods in `sys/dev/drm/amd/amdgpu/amdgpu_bios.c` free
`adev->bios` on their validation-failure path **without NULLing the pointer**:

| Method | alloc | kfree-without-NULL site |
|---|---|---|
| `igp_read_bios_from_vram` | amdgpu_bios.c:106 | amdgpu_bios.c:116 |
| `amdgpu_read_bios` | amdgpu_bios.c:135 | amdgpu_bios.c:145 |
| `amdgpu_read_bios_from_rom` | amdgpu_bios.c:174 | amdgpu_bios.c:185 |
| `amdgpu_atrm_get_bios` | amdgpu_bios.c:338 | amdgpu_bios.c:354 |
| `amdgpu_acpi_vfct_bios` | amdgpu_bios.c:423 (`kmemdup`) | amdgpu_bios.c:428 |

When every method fails, `amdgpu_get_bios()` (amdgpu_bios.c:446-477) prints
"Unable to locate a BIOS ROM" and returns false with `adev->bios` **dangling**
(the value left by whichever method touched it last — note the fallback
order and which methods clear the pointer first):

* `amdgpu_read_bios()` NULLs it at entry (amdgpu_bios.c:128) but re-dangles
  at :145 if the mapped ROM content is invalid.
* `amdgpu_read_bios_from_rom()` never NULLs at entry — early failures
  preserve the dangling pointer from the previous stage (amdgpu_bios.c:157-169).
* For CIK/VI ASICs the **final** fallback (`amdgpu_read_disabled_bios()`,
  amdgpu_bios.c:367-373) dispatches to `cik_read_disabled_bios()` (cik.c:890,
  calls `amdgpu_read_bios` at cik.c:922) / `vi_read_disabled_bios()` (vi.c:374,
  calls `amdgpu_read_bios` at vi.c:406) — both re-enter the function whose
  failure path dangles. For SOC15+ `soc15_read_disabled_bios()` (soc15.c:236-240)
  returns false without touching the pointer, preserving the dangle from
  `amdgpu_read_bios_from_rom()` or the earlier `amdgpu_read_bios()`.

The teardown chain then double-frees:

1. `amdgpu_device_init()` maps `adev->rmmio` **before** the BIOS read
   (amdgpu_device.c:2473), so the `rmmio == NULL` bypass in unload
   (amdgpu_kms.c:84) is NOT taken.
2. `amdgpu_get_bios()` fails → `r = -EINVAL; goto failed;`
   (amdgpu_device.c:2536-2539) → `failed:` returns r (amdgpu_device.c:2709-2716).
3. `amdgpu_driver_load_kms()` (amdgpu_kms.c:205-209) sees `r != 0` →
   `goto out` → calls `amdgpu_driver_unload_kms(dev)` (amdgpu_kms.c:239-246).
4. `amdgpu_driver_unload_kms()` → `adev->rmmio != NULL` → passes the guard →
   `amdgpu_device_fini(adev)` (amdgpu_kms.c:84-99).
5. `amdgpu_device_fini()` ends with the **unconditional**
   `kfree(adev->bios); adev->bios = NULL;` (amdgpu_device.c:2762-2763)
   → **second free of the same pointer** (first free happened at one of the
   five sites above, during `amdgpu_get_bios()`).

Between the two frees, `amdgpu_device_fini()` runs `amdgpu_irq_disable_all`,
`amdgpu_ib_pool_fini`, `amdgpu_fence_driver_fini`, `amdgpu_pm_sysfs_fini`,
`amdgpu_fbdev_fini`, `amdgpu_device_ip_fini`, `amdgpu_atombios_fini`
(amdgpu_device.c:2727-2760) — a nontrivial alloc/free window in which the
64K–256K chunk (M_DRM/kmalloc zone) can be re-purposed by another
allocation, converting the double-free into a free-of-live-object (UAF-style
corruption) or a slab free-list corruption. On DragonFly's allocator the
least-bad outcome is a panic; the worst is silent heap corruption.

## Threat model / reachability

* **Not** runtime-triggerable by an unprivileged user on a healthy system
  (probe happens at boot / driver load only; root-only `kldload`).
* Preconditions are realistic in exactly the environments this driver runs:
  - **GPU passthrough VMs** (cloud/VFIO): ROM BAR often unmapped or garbage;
    the guest kernel image is attacker-influenced by the VM admin — this is
    the same trust boundary as the known ATRM findings DF-1950/DF-1951.
  - **Crafted ACPI** (VFCT/ATRM): firmware-supplied tables decide which
    methods allocate/free — same surface as DF-4605 (amdgpu_acpi).
  - Broken/misprogrammed ROM shadow or failing hardware.
* Impact class: kernel heap double-free → memory corruption (panic probable,
  corruption possible). Hence Medium severity, memcorrupt bucket.

## Exploit chain

Not developed: no reachable target on the audit guest. On a system where an
attacker controls the VM/firmware config that induces the all-methods-fail
probe, the primitive is a same-size re-free of an M_DRM allocation at a
semi-controllable time (between the two frees the teardown path performs
several allocations the attacker can heap-groom from userspace only if the
driver attach races other subsystems — speculative, not claimed).

## Fix

`fix.diff` (verified `git apply --check` clean against the pristine tree):
NULL `adev->bios` at all five live kfree sites in amdgpu_bios.c so
`amdgpu_device_fini()`'s unconditional kfree sees NULL. (The sixth site,
amdgpu_bios.c:213, is inside `#if 0`.) One-line-per-site, matches the
pattern already used by the success paths (`adev->bios = NULL` at function
entry) and by `amdgpu_device_fini` itself.

## Fix validation

not_testable — the baseline could not be reproduced on this guest (no AMD
hardware), so a patched-kernel A/B run is impossible here. The fix is
trivially correct by inspection: it breaks the only path that leaves the
dangling pointer (verified there is no other consumer of `adev->bios`
between `amdgpu_get_bios()` failure and `amdgpu_device_fini()`'s kfree —
`amdgpu_atombios_init()` is only reached on success,
amdgpu_device.c:2542).

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