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

Use-after-free in reset functions when kzalloc fails (connector/crtc/plane state)

Summary

amdgpu_dm_connector_funcs_reset (:3190), dm_crtc_reset_state (:2961), dm_drm_plane_reset (:3426) all free existing state then only assign new state inside if(state)/if(WARN_ON). On OOM connector->state/crtc->state/plane->state left pointing at freed memory. Unprivileged local user under memory pressure triggers reset -> UAF on next property read or state duplication. Info leak of freed heap + potential code execution with slab grooming. Fix: always call __drm_atomic_helper_*_reset even on OOM (assigns NULL).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1153 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict Full source trace + reachability analysis (3 reset functions) 4.1 KB ↓ raw
fix.diff suggested-fix Assign NULL to *->state on OOM in all 3 reset fns (git apply-able, validated) 818 B view raw
build.sh build-doc Documents why no executable PoC is possible 350 B view raw
run.sh run-doc Documents why runtime trigger is impossible on this guest 176 B view raw
env.txt environment uname + kernel symbol inventory proving amdgpu absent 348 B view raw
README.md readme human reproduce doc 826 B ↓ 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
README.md readme human reproduce doc
↓ download raw

DF-1153 β€” Use-After-Free in amdgpu_dm reset functions on OOM

Result

NOT REPRODUCED on this guest. Real source-level defense-in-depth bug; fix authored. See VERDICT.md for the full analysis.

Build & run

./build.sh    # documents why no executable PoC is possible
./run.sh      # documents why runtime trigger is impossible

Reachability summary

  • amdgpu_dm is part of amdgpu, not in X86_64_GENERIC (0 symbols)
  • No AMD GPU on the QEMU guest, no /dev/dri
  • Trigger needs memory pressure during DRM atomic reset on AMD hardware

Fix

fix.diff β€” assign NULL to *->state on the OOM path of all three reset functions (amdgpu_dm_connector_funcs_reset, dm_crtc_reset_state, dm_drm_plane_reset) at sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c. Applies cleanly with git apply.

VERDICT.md verdict Full source trace + reachability analysis (3 reset functions)
↓ download raw

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

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

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

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.

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.

Fix verification

not_testable

git apply --check validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. amdgpu_dm reset functions UAF on kzalloc OOM. amdgpu not in GENERIC.