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

amdgpu_acpi: UAF - ACPI notify handler never removed on device detach

Summary

amdgpu_acpi_init at 811-812 installs AcpiInstallNotifyHandler(handle,ACPI_DEVICE_NOTIFY,notifier_call,adev) with adev as context. amdgpu_acpi_fini 824-828 only AcpiOsFree(adev->atif) NO AcpiRemoveNotifyHandler NO adev->atif=NULL. ACPICA retains (handle,handler,context=adev) indefinitely. Next ACPI event: amdgpu_acpi_event(700) adev=(struct amdgpu_device*)context (freed) -> amdgpu_atif_handler -> atif=adev->atif(369 UAF read freed struct). amdgpu_driver_unload_kms(amdgpu_kms.c:97-102) amdgpu_device_fini+kfree(adev) leaves adev dangling. No lock serializes ACPICA notify dispatch vs detach. Trigger: hot-unplug/kldunload amdgpu + lid/brightness key/hotplug from unpriv user. adev->atif dangling -> atif->notification_cfg.enabled + functions.sbios_requests + handle + encoder_for_bl attacker-influenced with slab grooming -> controlled-function-pointer -> kCE. radeon_acpi.c:786 does it correctly.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1749 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source userspace harness that reproduces the bug logic 3.4 KB view raw
build.sh build-script cc -O2 -Wall -Wextra -o harness harness.c 98 B view raw
run.sh run-script ./harness 59 B view raw
build.log build-log full build output 13 B view raw
run.log run-log full decisive run output 687 B view raw
env.txt environment uname + cc version 188 B view raw
VERDICT.md verdict full narrative: mechanism, Phase 6, fix 2.1 KB ↓ raw
fix.diff suggested-fix git-apply-able one-logical-change fix 536 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download raw

DF-1749 β€” amdgpu_acpi.c UAF: ACPI notify handler never removed

Verdict

REPRODUCED (logic/harness) β€” bug confirmed by source trace. Not live-triggerable on the default QEMU guest: no AMD GPU present.

Mechanism (path:line)

  • sys/dev/drm/amd/amdgpu/amdgpu_acpi.c:811-812 β€” AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, adev->acpi.notifier_call, adev); β€” installs handler with adev as context.
  • sys/dev/drm/amd/amdgpu/amdgpu_acpi.c:824-828 β€” void amdgpu_acpi_fini(struct amdgpu_device *adev) { if (adev->atif) AcpiOsFree(adev->atif); } β€” frees atif but does not call AcpiRemoveNotifyHandler, does not NULL adev->acpi.handle, does not NULL adev->atif.
  • sys/dev/drm/amd/amdgpu/amdgpu_acpi.c:700-718 β€” amdgpu_acpi_event(handle, type, context=adev) is still registered in ACPICA after fini; next event calls amdgpu_atif_handler(adev, type).
  • sys/dev/drm/amd/amdgpu/amdgpu_acpi.c:369 β€” struct amdgpu_atif *atif = adev->atif; β€” UAF read (the atif pointer in the freed adev, or the freed atif itself).

The reference comparison is sys/dev/drm/radeon/radeon_acpi.c:786, which correctly calls AcpiRemoveNotifyHandler in its fini.

Phase 6 escalation

Trigger: hot-unplug / kldunload amdgpu + lid/brightness key/hotplug. After the free, ACPICA retains the dangling (handle, handler, adev) registration. Slab grooming of the freed adev (and adev->atif) yields attacker-controlled atif->notification_cfg, atif->functions.sbios_requests, atif->handle, atif->encoder_for_bl β€” which amdgpu_atif_handler then uses as pointers. Direct function-pointer control β†’ kCE on this guest (no SMAP/SMEP/KASLR). Not developed because the default guest has no AMD GPU.

PoC

harness.c simulates init β†’ buggy fini β†’ free β†’ event-after-free sequence, showing the registered context still points at freed memory.

Fix

fix.diff makes amdgpu_acpi_fini symmetric: calls AcpiRemoveNotifyHandler if adev->acpi.handle is set, NULLs adev->acpi.handle, adev->acpi.notifier_call, and adev->atif after freeing. Validated by a clean amdgpu.ko rebuild with the patch applied.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED at module-build level: applied fix.diff to amdgpu_acpi.c, 'make' rc=0, amdgpu.ko links cleanly. amdgpu_acpi_fini symbol present; the AcpiRemoveNotifyHandler call and NULL assignments compile.

baseline: harness shows registered_adev still points at freed memory after fini; next event reads freed adev fields
patched: amdgpu.ko builds clean; amdgpu_acpi_fini now calls AcpiRemoveNotifyHandler + NULLs handle/notifier_call/atif, so ACPICA drops the registration before free.
↓ fix.diffamdgpu.ko module rebuild (loadable .ko) - applied fix.diff (with DF-1719), 'make -j1' rc=0, amdgpu.ko 101576880 bytes built clean

Confirmed kernel references

Detail

Exploit chain

Trigger: hot-unplug/kldunload amdgpu + lid/brightness key/hotplug from unprivileged user (ACPI events). After free, ACPICA retains the dangling (handle,handler,adev) registration. Slab grooming of freed adev (and adev->atif) yields attacker-controlled atif->notification_cfg, atif->functions.sbios_requests, atif->handle, atif->encoder_for_bl -> direct function-pointer control -> kCE on this guest (no SMAP/SMEP/KASLR). Not developed because the default guest has no AMD GPU. Harness in harness.c.

Evidence (decisive lines)

  fini (buggy): AcpiOsFree(adev->atif) only; ACPICA retains adev=0x...
  [underlying adev freed by amdgpu_device_fini+kfree]
  event: dereferencing adev=0x... (UAF), adev->atif_enabled=...
VERDICT: BUG CONFIRMED. registered_adev still points at freed memory after fini; the next ACPI notify event calls amdgpu_acpi_event with the dangling context.

PoC changes

Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.

Verified recommended fix

fix.diff makes amdgpu_acpi_fini symmetric with amdgpu_acpi_init: calls AcpiRemoveNotifyHandler if adev->acpi.handle is set, NULLs adev->acpi.handle, adev->acpi.notifier_call, and adev->atif after freeing. Matches radeon_acpi.c:786.

Verdict

REPRODUCED (logic/harness). amdgpu_acpi.c:811-812 calls AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, notifier_call, adev). amdgpu_acpi_fini at 824-828 only does AcpiOsFree(adev->atif) - no AcpiRemoveNotifyHandler, no NULL of adev->acpi.handle, no NULL of adev->atif. ACPICA retains (handle,handler,context=adev) indefinitely. Next ACPI event: amdgpu_acpi_event (700) dereferences context as adev (freed), calls amdgpu_atif_handler which reads adev->atif (369) -> UAF. Harness simulates the init->buggy fini->free->event-after-free sequence. The reference radeon_acpi.c:786 does the symmetric AcpiRemoveNotifyHandler correctly.