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

Latent unchecked res_pool deref + lockless dc->current_state walk in dead public API dc_plane_get_status()

Summary

Validates current_state but derefs res_pool unchecked and walks current_state->pipe_ctx[] with no reference/lock while the commit path swaps+frees it unlocked - zero callers in this tree (dead), but the next vendor sync makes it reachable (NULL-deref/UAF-read class). Retain-and-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5144 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 4.8 KB ↓ raw
verdict.json β€” 2.7 KB view raw
manifest.json β€” 747 B view raw

DF-5144 β€” Latent unchecked deref + lockless state walk in dead public API dc_plane_get_status()

File: sys/dev/drm/amd/display/dc/core/dc_surface.c:98-129 Severity: Info (hardening / defense-in-depth β€” no demonstrated impact in this tree) Status: Phase V skipped per contract (Info severity; no PoC built, no guest run).

What this is

A pass-2 (GLM 5.3) finding on dc_surface.c. Three latent defects in the public DC API dc_plane_get_status(), which currently has zero callers anywhere in sys/ (declared at sys/dev/drm/amd/display/dc/dc.h:563, defined at dc_surface.c:98, never called β€” amdgpu_dm.c in this tree does not invoke it):

  1. dc_surface.c:118 β€” core_dc->res_pool->pipe_count is dereferenced with no NULL check on core_dc->res_pool. The function carefully checks plane_state, plane_state->ctx, plane_state->ctx->dc (:105-107) and core_dc->current_state (:115) but omits res_pool.
  2. dc_surface.c:118-126 β€” the loop walks core_dc->current_state->res_ctx.pipe_ctx[] with no reference and no lock. dc->current_state is swapped and the old state freed in the commit path (sys/dev/drm/amd/display/dc/core/dc.c:1026-1030, :1649-1650) with no internal DC lock; safety depends entirely on callers serializing against commits. A caller that does not hold the modeset locks races a UAF read.
  3. dc_surface.c:125 β€” core_dc->hwss.update_pending_status(pipe_ctx) is invoked without a NULL check. Today every hw sequencer populates it (dce110_hw_sequencer.c:2883, dcn10_hw_sequencer.c:2719; dce80 delegates to the dce110 table at dce80_hw_sequencer.c:77), so this is only a robustness gap.

Why no PoC

Info severity β†’ Phase V is skipped per the reviewer contract ("Skip … for Low/Info hardening findings"). The function is unreachable in the shipped tree, so there is no trigger to build: no userspace path reaches dc_plane_get_status().

Reproduce the analysis (static)

rg -n "dc_plane_get_status" sys/          # only dc.h declaration + dc_surface.c definition
sed -n '98,129p' sys/dev/drm/amd/display/dc/core/dc_surface.c
sed -n '1020,1035p' sys/dev/drm/amd/display/dc/core/dc.c   # lockless current_state swap/free

Take a state reference, check res_pool, see VERDICT.md for the diff.

VERDICT.md
↓ download raw

DF-5144 β€” VERDICT

Status: untested (Phase V skipped β€” Info severity, no guest run). Impact: none (function is dead code in this tree).

Narrative

Pass-2 re-audit of sys/dev/drm/amd/display/dc/core/dc_surface.c (210 LOC), known finding DF-2140 (construct() NULL derefs at :42-46) excluded from re-reporting.

The pass-2 hunt concentrated on the destruct() pairing, refcount lifetimes, and the API surface of the plane-state object. Findings:

  • destruct() pairing is correct. dc_gamma_release(&plane_state->gamma_correction) (dc_surface.c:52) takes struct dc_gamma ** and NULLs the caller's pointer (dc_surface.c:159-163); dc_transfer_func_release(plane_state->in_transfer_func) (dc_surface.c:55-56) takes a single pointer and does not NULL it (dc_surface.c:190-193), but destruct() NULLs the field manually at :57. The only other in-tree release of a plane's transfer func is the fill_plane_attributes() error path (amdgpu_dm.c:2454-2458), which likewise releases-then-NULLs before the caller's dc_plane_state_release() (amdgpu_dm.c:5477-5480); destruct() then skips the NULL field. No double-release exists in this tree. The ** vs * API asymmetry between dc_gamma_release and dc_transfer_func_release is a trap for future callers but not a bug today.

  • Refcount pairing in the DC core is balanced β€” retain on attach (dc_resource.c:1397-1398), release on detach (dc_resource.c:1477-1479), release-all in dc_resource_state_destruct (dc_resource.c:2556-2560), retain-all in copy-construct (dc_resource.c:2593-2596).

  • enable_surface_flip_reporting() (dc_surface.c:64-69) is dead code β€” no callers; its unchecked controller_id + DC_IRQ_SOURCE_PFLIP1 - 1 enum arithmetic is latent only.

  • dc_plane_get_status() (dc_surface.c:98-129) is dead code β€” declared at dc.h:563, zero callers tree-wide. It carries three latent defects:

  1. core_dc->res_pool dereferenced unchecked at dc_surface.c:118 (core_dc->res_pool->pipe_count) while plane_state, ctx, ctx->dc (:105-107) and current_state (:115) are all checked.
  2. Lockless iteration over core_dc->current_state->res_ctx.pipe_ctx[] at :118-126: dc->current_state is swapped and the old state freed at dc.c:1026-1030 / :1649-1650 with no internal DC lock β€” the function relies entirely on caller-side serialization. A future caller that does not hold the DRM modeset locks races a use-after-free read.
  3. Unchecked function-pointer call core_dc->hwss.update_pending_status(pipe_ctx) at :125 β€” currently populated by every hw sequencer (dce110:2883, dcn10:2719; dce80 delegates via dce80_hw_sequencer.c:77), so robustness only.

Why this matters despite being dead today: this is vendor DC code tracking Linux amdgpu_dm, where dc_plane_get_status() is called from user-visible atomic-check paths. The next driver sync lights these up. Filed as Info.

Exploit chain

None β€” unreachable in this tree (no callers). Impact ceiling if a future caller appears without modeset locks: kernel UAF read / NULL deref (panic/DoS class).

Fix validation

Not performed β€” Info-severity hardening finding; Phase V fix validation is skipped per the reviewer contract. The recommended fix is in README.md / fix.diff-form below (authored against the read-only sys/ tree, never applied):

--- a/sys/dev/drm/amd/display/dc/core/dc_surface.c
+++ b/sys/dev/drm/amd/display/dc/core/dc_surface.c
@@ -112,10 +112,14 @@ const struct dc_plane_status *dc_plane_get_status(
    plane_status = &plane_state->status;
    core_dc = plane_state->ctx->dc;

+   if (core_dc->res_pool == NULL)
+       return NULL;
    if (core_dc->current_state == NULL)
        return NULL;

+   dc_retain_state(core_dc->current_state);
    for (i = 0; i < core_dc->res_pool->pipe_count; i++) {
        struct pipe_ctx *pipe_ctx =
                &core_dc->current_state->res_ctx.pipe_ctx[i];

        if (pipe_ctx->plane_state != plane_state)
            continue;

+       if (core_dc->hwss.update_pending_status == NULL)
+           continue;
        core_dc->hwss.update_pending_status(pipe_ctx);
    }
+   dc_release_state(core_dc->current_state);

    return plane_status;

(dc_retain_state/dc_release_state already exist: dc.c:1091/1103.)

Bottom line

No exploitable defect in the shipped tree; one Info hardening note on a dead public API whose upstream callers will arrive with the next vendor sync.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix validation not performed: Info-severity hardening finding on dead code; Phase V skipped per contract.

n/a
per-fix-DF-5144

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5144/README.md (analysis + static reproduce commands); findings/poc/DF-5144/VERDICT.md (full narrative incl. destruct()-pairing and refcount negative results and the recommended diff)

PoC changes

No PoC β€” the function has no callers in this tree, so no trigger exists to build; Phase V skipped for Info severity per contract.

Verified recommended fix

In dc_plane_get_status(): NULL-check core_dc->res_pool, take dc_retain_state()/dc_release_state() around the current_state iteration, and NULL-check hwss.update_pending_status (see VERDICT.md diff).

Verdict

Info hardening finding from the pass-2 audit of dc_surface.c: dc_plane_get_status() (dc_surface.c:98-129) is dead code in this tree (declared dc.h:563, zero callers) but carries three latent defects β€” unchecked core_dc->res_pool deref at dc_surface.c:118, lockless iteration over dc->current_state (swapped/freed at dc.c:1026-1030 with no internal lock) at :118-126, and an unchecked hwss.update_pending_status call at :125. No impact as shipped; next vendor sync that ports the upstream amdgpu_dm caller would make these reachable (UAF-read/NULL-deref class). Phase V skipped per contract (Info severity); no PoC built, no guest run.