β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2204

No device-removal path and non-idempotent add: stale/UAF list nodes and list corruption on re-add

Summary

No amdgpu_xgmi_remove_device/amdgpu_xgmi_fini anywhere in tree (zero list_del of xgmi.head). Once device list_add_tail-d into hive (line 85) stays forever even after amdgpu_device torn down and freed. Later amdgpu_xgmi_add_device for any device in same hive iterates freed node (UAF) via list_for_each_entry (lines 86 and 98). Separately add is not idempotent: never checks whether adev already linked so second call list_add_tail on already-linked node corrupts doubly-linked list can turn loops into infinite loops or visits to arbitrary addresses. Trigger: root unbind/rebind GPU or driver reset path re-enters amdgpu_device_init without first removing node. Impact: kernel memory corruption UAF read/write of freed amdgpu_device or list corruption infinite loop panic.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2204 Β· 8 files
FileTypeDescriptionSize
README.md readme claim, exhaustive-grep evidence, fix summary 3.4 KB ↓ raw
VERDICT.md verdict no-remove-path + non-idempotent-add trace + 3-file fix 3.7 KB ↓ raw
fix.diff suggested-fix idempotency guard + new amdgpu_xgmi_remove_device + fini hook (xgmi.c, amdgpu.h, amdgpu_device.c) 2.7 KB view raw
build.sh build-script applies fix.diff + rebuilds amdgpu.ko in guest 564 B view raw
run.sh run-script HW-gated no-op runner 414 B view raw
build_baseline.log build-log unpatched amdgpu.ko build, rc=0, -Werror 1.0 MB ↓ download
build_patched.log build-log patched amdgpu.ko rebuild, amdgpu_xgmi.o + amdgpu_device.o recompiled, rc=0, -Werror 12.6 KB view raw
env.txt environment uname, cc, kern.version 376 B view raw
README.md readme claim, exhaustive-grep evidence, fix summary
↓ download raw

DF-2204 β€” amdgpu_xgmi: no device-removal path, stale/UAF; non-idempotent add (Low)

Claim

Two related defects in sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c:

  1. No removal path. There is no amdgpu_xgmi_remove_device() / amdgpu_xgmi_fini() anywhere in the tree (zero list_del of xgmi.head β€” confirmed by grep). Once a device is list_add_tail()-ed into hive->device_list (line 85) it stays linked forever, even after amdgpu_device is torn down and freed. Any later amdgpu_xgmi_add_device() for any device in the same hive then iterates the freed node (UAF) via list_for_each_entry at lines 86 and 98.

  2. Non-idempotent add. amdgpu_xgmi_add_device() never checks whether adev is already linked. A second call (e.g. driver reset / rebind re-entering amdgpu_device_init without first removing the node) list_add_tail()s an already-linked node, corrupting the doubly-linked list (infinite loops / visits to arbitrary addresses).

Verification approach

HW-gated / source-only. Reachable only on Vega20 hardware with a driver reset / unbind-rebind cycle. No GPU on this audit guest. Per the task brief, source-only confirmation is acceptable.

  1. grep the entire sys/dev/drm tree for xgmi.head / amdgpu_xgmi_remove / amdgpu_xgmi_fini β€” confirm the only list_add_tail/iterators are in amdgpu_xgmi.c and there is no list_del.
  2. Confirm list_add_tail is unconditional (no list_empty check).
  3. Confirm amdgpu_device_fini() (the natural teardown counterpart to amdgpu_device_init's amdgpu_xgmi_add_device() call) does not call any xgmi cleanup.
  4. Author fix.diff (idempotency guard + new amdgpu_xgmi_remove_device + hook into amdgpu_device_fini).
  5. Phase 8 β€” apply all 5 batched fixes and rebuild amdgpu.ko with -Werror.

Source trace (confirmed)

Files

  • VERDICT.md β€” full narrative.
  • fix.diff β€” three-part fix in three files:
  • amdgpu_xgmi.c: add idempotency guard (if (!list_empty(...)) goto exit) before the existing list_add_tail; append amdgpu_xgmi_remove_device() that does list_del + INIT_LIST_HEAD under the xgmi_mutex.
  • amdgpu.h: declare void amdgpu_xgmi_remove_device(struct amdgpu_device *adev);.
  • amdgpu_device.c: call amdgpu_xgmi_remove_device(adev) near the top of amdgpu_device_fini(), before any of the device's memory is freed.
  • build_baseline.log β€” unpatched amdgpu.ko build, rc=0, -Werror.
  • build_patched.log β€” patched amdgpu.ko rebuild (amdgpu_xgmi.o + amdgpu_device.o recompiled), rc=0, -Werror.
  • env.txt β€” guest environment.

Reproduce

./build.sh    # applies fix.diff + rebuilds amdgpu.ko
./run.sh      # HW-gated no-op (see VERDICT.md)
VERDICT.md verdict no-remove-path + non-idempotent-add trace + 3-file fix
↓ download raw

DF-2204 β€” VERDICT

Verdict: REPRODUCED (source-only, HW-gated).

Class: UAF + list corruption (memory corruption).

Mechanism

Two related lifetime defects in the amdgpu XGMI bookkeeping.

(1) No removal path β†’ stale node β†’ UAF

amdgpu_xgmi_add_device() (sys/dev/drm/amd/amdgpu/amdgpu_xgmi.c) links a device into a hive:

/* amdgpu_xgmi.c:85 */
list_add_tail(&adev->gmc.xgmi.head, &hive->device_list);

There is no counterpart anywhere in the tree. Verified by exhaustive grep of sys/dev/drm:

$ grep -rn 'list_del.*xgmi\.head\|amdgpu_xgmi_remove\|amdgpu_xgmi_fini' sys/dev/drm
(0 matches)

The only operations on xgmi.head in the entire tree are the list_add_tail at line 85 and the two iterators at lines 86 and 98.

The natural teardown counterpart of the init call (amdgpu_device_init β†’ amdgpu_xgmi_add_device(adev) at amdgpu_device.c:1691) is amdgpu_device_fini() (amdgpu_device.c:2727). Reading the whole function (lines 2727-2779) confirms it never calls any xgmi cleanup. So once a Vega20 GPU is added to a hive, its gmc.xgmi.head node stays linked into hive->device_list forever β€” across kfree(adev), across module unload, across driver rebind.

The next amdgpu_xgmi_add_device() for any other device in the same hive then list_for_each_entry-walks hive->device_list (lines 86 and 98) and dereferences the freed amdgpu_device β†’ classic UAF read.

(2) Non-idempotent add β†’ list corruption

list_add_tail() at line 85 is unconditional. There is no list_empty(&adev->gmc.xgmi.head) guard. A second amdgpu_xgmi_add_device() for the same adev (which a driver reset / unbind-rebind path will trigger, because amdgpu_device_init re-runs without first removing the node) re-links an already-linked node. In a doubly-linked list, that produces a corrupted structure: the same node appears twice, loops can become infinite, or iterators visit arbitrary addresses.

Trigger surface / reachability

amdgpu_xgmi_add_device() early-returns for non-Vega20 ASICs and APUs, so this is Vega20-only. The realistic trigger is a driver reset / rebind cycle on a Vega20 host (root or root-equivalent; unbind/rebind of a PCI device). On this audit guest there is no GPU, so the UAF / list corruption cannot be exercised live; the bug is confirmed at the source level and the fix compiles cleanly (Phase 8).

Fix

fix.diff is a three-part patch:

  • amdgpu_xgmi.c (part 1): add an idempotency guard before the existing list_add_tail β€” if adev->gmc.xgmi.head is not empty the device is already linked, log a warning and goto exit with ret = 0.
  • amdgpu_xgmi.c (part 2): append a new amdgpu_xgmi_remove_device(adev) that, under the existing xgmi_mutex, does list_del + INIT_LIST_HEAD if the node is linked.
  • amdgpu.h: declare the new function next to amdgpu_xgmi_add_device.
  • amdgpu_device.c: call amdgpu_xgmi_remove_device(adev) near the top of amdgpu_device_fini() (right after adev->shutdown = true), before any of the device's memory or MMIO is freed.

Phase 8 build validation

Applied fix.diff (plus the four other batched DRM fixes) to the in-guest /usr/src, rebuilt amdgpu.ko with -Werror: * baseline (unpatched) amdgpu.ko: rc=0 (build_baseline.log). * patched amdgpu.ko: rc=0, amdgpu_xgmi.o and amdgpu_device.o recompiled (build_patched.log); no warnings, no errors.

Verdict

REPRODUCED at source level (HW-gated; no live repro possible on guest). Both halves of the claim β€” no removal path and non-idempotent add β€” are confirmed by exhaustive grep and by reading amdgpu_device_fini end-to-end. The fix compiles cleanly under -Werror.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched reproduced

amdgpu.ko baseline+patched rc=0 -Werror (3-file fix)

amdgpu.ko baseline+patched rc=0 -Werror (3-file fix)
↓ fix.diffmodule build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (HW-gated)

Evidence (decisive lines)

HW-gated. Source-confirmed: no list_del for xgmi.head anywhere -> stale UAF + non-idempotent add.

Verified recommended fix

HW-gated. Source-confirmed: no list_del for xgmi.head anywhere -> stale UAF + non-idempotent add.

Verdict

HW-gated. Source-confirmed: no list_del for xgmi.head anywhere -> stale UAF + non-idempotent add.