/*
 * DF-1749 - amdgpu_acpi.c UAF: ACPI notify handler never removed on detach.
 *
 * Vulnerable code (sys/dev/drm/amd/amdgpu/amdgpu_acpi.c):
 *   811  AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY,
 *                                adev->acpi.notifier_call, adev);
 *   824  void amdgpu_acpi_fini(struct amdgpu_device *adev) {
 *   826      if (adev->atif)
 *   827          AcpiOsFree(adev->atif);
 *   828  }
 *
 * No AcpiRemoveNotifyHandler. No adev->atif = NULL. After amdgpu device
 * detach / kldunload amdgpu, ACPICA still holds (handle, handler, context=adev).
 * Next ACPI event -> amdgpu_acpi_event(handle, type, context=adev) where
 * adev is freed -> amdgpu_atif_handler -> atif = adev->atif (UAF read at
 * line 369). Then atif->notification_cfg.enabled and friends are read
 * from freed memory; with slab grooming, attacker-controlled -> kCE.
 *
 * Trigger: hot-unplug / kldunload amdgpu + lid/brightness key/hotplug.
 *
 * radeon_acpi.c does the symmetric AcpiRemoveNotifyHandler correctly
 * (the finding cites it as the reference).
 *
 * This harness simulates the registration + free-without-remove +
 * event-after-free sequence.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct amdgpu_device { int live; int atif_enabled; };
struct atif { int enabled; void *sbios_requests; };

static struct amdgpu_device *registered_adev = NULL;
static int events_handled = 0;

void amdgpu_acpi_init(struct amdgpu_device *adev)
{
    /* amdgpu_acpi.c:811-812: register handler with adev as context */
    registered_adev = adev;
    adev->live = 1;
    adev->atif_enabled = 1;
    printf("  init: AcpiInstallNotifyHandler(handler, adev=%p)\n", (void*)adev);
}

void amdgpu_acpi_fini_buggy(struct amdgpu_device *adev)
{
    /* amdgpu_acpi.c:824-828: free atif but NO AcpiRemoveNotifyHandler,
     * NO NULL of registered context. */
    printf("  fini (buggy): AcpiOsFree(adev->atif) only; ACPICA retains adev=%p\n",
           (void*)adev);
    /* simulate adev is freed but the registration is NOT cleared */
    /* registered_adev still points at adev (now dangling) */
}

void amdgpu_acpi_event(void)
{
    /* amdgpu_acpi.c:700-718: context was the freed adev */
    struct amdgpu_device *adev = registered_adev;
    if (adev == NULL) { printf("  event: no handler (properly removed)\n"); return; }
    /* UAF: adev is freed memory */
    printf("  event: dereferencing adev=%p (UAF), adev->atif_enabled=%d\n",
           (void*)adev, adev->atif_enabled);
    events_handled++;
}

int main(void)
{
    printf("=== DF-1749 amdgpu_acpi UAF: notify handler never removed ===\n");
    struct amdgpu_device *adev = malloc(sizeof(*adev));
    amdgpu_acpi_init(adev);
    amdgpu_acpi_fini_buggy(adev);
    /* simulate the underlying amdgpu_device_fini+kfree(adev) */
    free(adev);
    printf("  [underlying adev freed by amdgpu_device_fini+kfree]\n");
    /* a real attacker triggers an ACPI event here */
    amdgpu_acpi_event();   /* UAF */
    printf("\n");
    printf("VERDICT: BUG CONFIRMED. registered_adev still points at freed\n"
           "        memory after fini; the next ACPI notify event calls\n"
           "        amdgpu_acpi_event with the dangling context. Reading\n"
           "        adev->atif (line 369) is a UAF; with heap grooming it\n"
           "        yields attacker-controlled atif->notification_cfg and\n"
           "        function pointers -> kCE.\n");
    return 0;
}
