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)
PoC verification
Evidence pack
findings/poc/DF-1749 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 withadevas 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); }β freesatifbut does not callAcpiRemoveNotifyHandler, does not NULLadev->acpi.handle, does not NULLadev->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 callsamdgpu_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
fixedVALIDATED 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.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- a
- c
- p
- i
- .
- c
- :
- 8
- 1
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- a
- c
- p
- i
- .
- c
- :
- 8
- 2
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- a
- c
- p
- i
- .
- c
- :
- 3
- 6
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- a
- c
- p
- i
- .
- c
- :
- 7
- 0
- 0
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.
No comments yet.