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)
PoC verification
Evidence pack
findings/poc/DF-5144 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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):
dc_surface.c:118βcore_dc->res_pool->pipe_countis dereferenced with no NULL check oncore_dc->res_pool. The function carefully checksplane_state,plane_state->ctx,plane_state->ctx->dc(:105-107) andcore_dc->current_state(:115) but omitsres_pool.dc_surface.c:118-126β the loop walkscore_dc->current_state->res_ctx.pipe_ctx[]with no reference and no lock.dc->current_stateis 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.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 atdce80_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
Recommended fix
Take a state reference, check res_pool, see VERDICT.md for the diff.
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) takesstruct 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 thefill_plane_attributes()error path (amdgpu_dm.c:2454-2458), which likewise releases-then-NULLs before the caller'sdc_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 betweendc_gamma_releaseanddc_transfer_func_releaseis 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 uncheckedcontroller_id + DC_IRQ_SOURCE_PFLIP1 - 1enum arithmetic is latent only. -
dc_plane_get_status()(dc_surface.c:98-129) is dead code β declared atdc.h:563, zero callers tree-wide. It carries three latent defects:
core_dc->res_pooldereferenced unchecked at dc_surface.c:118 (core_dc->res_pool->pipe_count) whileplane_state,ctx,ctx->dc(:105-107) andcurrent_state(:115) are all checked.- Lockless iteration over
core_dc->current_state->res_ctx.pipe_ctx[]at :118-126:dc->current_stateis swapped and the old state freed atdc.c:1026-1030/:1649-1650with 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. - 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_testableFix validation not performed: Info-severity hardening finding on dead code; Phase V skipped per contract.
n/a
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.
No comments yet.