# DF-1153 — Use-After-Free in amdgpu_dm reset functions on `kzalloc` failure

## Verdict
**NOT REPRODUCED** on this guest (defense-in-depth source bug confirmed;
path unreachable on the default GENERIC DragonFlyBSD kernel running in
QEMU).  Real source-level defect; fix.diff authored.

## Mechanism (source trace, bug confirmed real)
Three amdgpu_dm atomic-helper reset callbacks share the same broken
"destroy-then-alloc" pattern that leaves a dangling pointer on OOM:

### `amdgpu_dm_connector_funcs_reset` — `sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:3190`
```c
void amdgpu_dm_connector_funcs_reset(struct drm_connector *connector)
{
    struct dm_connector_state *state = to_dm_connector_state(connector->state);

    if (connector->state)
        __drm_atomic_helper_connector_destroy_state(connector->state);  /* clears fields, keeps pointer */

    kfree(state);                                  /* L3198: FREED */

    state = kzalloc(sizeof(*state), GFP_KERNEL);   /* L3200 */
    if (state) {                                   /* L3202: ONLYassigns on success */
        ...
        __drm_atomic_helper_connector_reset(connector, &state->base);  /* L3209: sets connector->state */
    }
    /* OOM path: connector->state STILL points at the freed memory => UAF */
}
```

### `dm_crtc_reset_state` — `sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:2961`
```c
static void dm_crtc_reset_state(struct drm_crtc *crtc)
{
    struct dm_crtc_state *state;

    if (crtc->state)
        dm_crtc_destroy_state(crtc, crtc->state);  /* L2966: frees */

    state = kzalloc(sizeof(*state), GFP_KERNEL);
    if (WARN_ON(!state))                           /* L2969 */
        return;                                    /* crtc->state STILL points at freed memory */

    crtc->state = &state->base;                    /* L2972: only on success */
    ...
}
```

### `dm_drm_plane_reset` — `sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:3426`
```c
static void dm_drm_plane_reset(struct drm_plane *plane)
{
    struct dm_plane_state *amdgpu_state = NULL;

    if (plane->state)
        plane->funcs->atomic_destroy_state(plane, plane->state);  /* L3431: frees */

    amdgpu_state = kzalloc(sizeof(*amdgpu_state), GFP_KERNEL);
    WARN_ON(amdgpu_state == NULL);

    if (amdgpu_state) {                            /* L3436: only on success */
        plane->state = &amdgpu_state->base;
        ...
    }
    /* OOM path: plane->state STILL points at the freed memory */
}
```

If `kzalloc()` fails (memory pressure), each function has already freed the
old state but bails out before assigning `NULL` (or a fresh object) to
`connector->state` / `crtc->state` / `plane->state`.  The next DRM call that
reads/duplicates that state dereferences freed memory → UAF.  Impact per the
finding: kernel-heap info leak (freed content surfaced to userspace via
property read / state duplication), with code-exec potential under slab
grooming.

## Why it does NOT reproduce on this guest
1. **amdgpu_dm is part of the amdgpu driver, not in GENERIC.**
   `nm /boot/kernel/kernel.debug | grep -ciE 'radeon|amdgpu'` ⇒ **0**;
   no amdgpu module is loaded.
2. **No AMD GPU in the QEMU guest** (serial-console VM, no PCI GPU).  Even
   with the module loaded there is no DRM connector / CRTC / plane for
   amdgpu_dm to instantiate, so none of the three reset callbacks is ever
   invoked.
3. **Trigger requires sustained memory pressure *during* a DRM atomic
   commit/reset ioctl on an amdgpu-managed object.**  None of these
   preconditions are achievable on the QEMU guest.

## Exploit chain
none — primitive (kernel UAF) requires amdgpu driver loaded + AMD hardware
+ memory pressure during a DRM ioctl.  None are present on this guest.

## PoC changes
none — no executable PoC is possible; the path is dead code in the running
kernel and depends on hardware this guest does not have.

## Recommended fix
Always assign `NULL` to the state pointer on the OOM path of each reset
function (mirroring the upstream linuxdrm helper contract which expects
the reset callback to leave `*->state == NULL` on failure).  See
`fix.diff` — applies cleanly with `git apply` (validated).  Matches the
finding markdown's proposal.
