dce110_apply_ctx_for_surface programs a front end for a head pipe whose plane was detached (plane_state NULL) -> kernel NULL-deref panic
Summary
The walk loops every pipe whose stream matches and unconditionally calls program_front_end_for_pipe -> set_default_colors which dereferences pipe_ctx->plane_state->color_space. The core deliberately produces head pipes with stream!=NULL && plane_state==NULL (dc_remove_plane_from_context: 'For head pipe detach surfaces from pipe'). Sibling code guards exactly this state (core/dc.c, dcn10_hw_sequencer.c, dc_stream.c) - the dce110 callee (inherited unchanged by DCE80/100/110/112/120, none override) does not. Trigger: atomic commit disabling the PRIMARY plane while an OVERLAY plane and the CRTC remain, then any commit reaching dc_commit_state. Any local user with /dev/dri/cardN access (root:video 0660) on DCE80..DCE120 hardware. Compile-tested on guest BUILD_RC=0; runtime hardware-gated; chain proven hop-by-hop at source. Read-only NULL deref -> panic. Guard-the-loop fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3946 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | doc | build/run/expected + source chain | 3.2 KB | β raw |
| VERDICT.md | verdict | verification narrative | 3.1 KB | β raw |
| trigger_dce110_headplane.c | trigger | raw DRM_IOCTL_MODE_ATOMIC trigger (3-commit sequence) | 5.1 KB | view raw |
| build.sh | script | build command | 116 B | view raw |
| run.sh | script | run command | 168 B | view raw |
| build.log | log | guest compile output | 355 B | view raw |
| run.log | log | guest run output (no DRI adapter β hardware-gated exit) | 355 B | view raw |
| fix.diff | suggested-fix | plane_state NULL guard in apply_ctx_for_surface | 748 B | view raw |
DF-3946 β dce110_apply_ctx_for_surface NULL plane_state dereference (kernel panic)
Severity: Medium (local DoS, unprivileged DRI user) Β· CWE-476
File: sys/dev/drm/amd/display/dc/dce110/dce110_hw_sequencer.c
Build / Run / Expected
cc -O2 -o trigger_dce110_headplane trigger_dce110_headplane.c # builds anywhere (raw ioctls)
./trigger_dce110_headplane /dev/dri/card0
On hardware with an AMD pre-DCN10 ASIC (DCE80/100/110/112/120 β all share
dce110_hw_sequencer_construct, see dce80_hw_sequencer.c, dce100_β¦,
dce112_β¦, dce120_hw_sequencer.c): executing the 3-commit atomic sequence
(primary+overlay on β primary off/overlay on β any commit that reaches
dc_commit_state) causes an immediate kernel page fault in
set_default_colors().
On the audit QEMU guest there is no AMD GPU, so runtime verification is inconclusive (hardware-gated); the reachability chain is proven at source level below.
Why it is real (source chain, every hop cited)
dc_remove_plane_from_context()βsys/dev/drm/amd/display/dc/core/dc_resource.c:1462-1467: removing a plane from the head pipe doespipe_ctx->plane_state = NULL; pipe_ctx->bottom_pipe = NULL;whilepipe_ctx->streamstays set ("For head pipe detach surfaces from pipe"). β a pipe withstream != NULL && plane_state == NULLis a normal, intentionally produced state.- Userland reaches that from any KMS client:
amdgpu_dm_atomic_checkβdm_update_planes_stateβdc_remove_plane_from_context(amdgpu_dm.c:5426) when an atomic commit disables a plane. With β₯1 remaining plane (the overlay/underlay bottom pipe)stream_status->plane_countstays β₯ 1. amdgpu_dm_atomic_commit_tailβdc_commit_state(dm->dc, dc_state)(amdgpu_dm.c:4837,dc_state=dm_state->context, set for every commit that touched planes,amdgpu_dm.c:4752-4755).dc_commit_stateβ for each non-mode-changed stream:dc->hwss.apply_ctx_for_surface(dc, streams[i], plane_count, context)(core/dc.c:955-959; second identical call at:992-997).dce110_apply_ctx_for_surface(dce110_hw_sequencer.c:2743-2763) walks all pipes withpipe_ctx->stream == streamβ including the head pipe from step 1 β and callsdce110_program_front_end_for_pipe()on it (line 2757) without anyplane_stateNULL check.- First dereference:
set_default_colors()atdce110_hw_sequencer.c:2095-2114, line 2100:default_adjust.in_color_space = pipe_ctx->plane_state->color_space;β read from NULL+offset β kernel page fault β panic.
Sibling code explicitly guards this exact state, proving the state is
anticipated elsewhere:
- core/dc.c:1521-1522: if (!pipe_ctx->plane_state) continue;
- dcn10_hw_sequencer.c:2386-2391: handles !pipe_ctx->plane_state.
- dc_stream.c cursor path guards !pipe_ctx->plane_state (core/dc_stream.c:251-256).
The caller-side guard (dc.c:1521) protects only the
dc_commit_updates_for_stream fast path; the dc_commit_state path
(dc.c:956/:992) has no such guard, and the dce80/dce100/dce110/dce112/
dce120 hwss tables do not override apply_ctx_for_surface.
Fix (also in fix.diff)
Skip pipes with no plane in the program loop, mirroring core/dc.c:1521.
DF-3946 β Verification Verdict
Status: inconclusive (hardware-gated; source-level chain certain) Impact demonstrated: none (panic expected on hardware) Confidence: certain (source), runtime not testable on this guest
Reproduction environment
- Audit guest: DragonFly x86-64 QEMU/KVM VM β no AMD GPU / no DRI adapter,
so the DRM/amdgpu path cannot execute at all.
trigger_dce110_headplane.ccompile-verifies only (see build.log). - Target hardware class: any amdgpu pre-DCN10 ASIC (DCE80/100/110/112/120):
Oland/Tonga/Fiji/Carrizo/Stoney/Polaris/Vega10 β all five hwss variants
inherit
dce110_apply_ctx_for_surface(verified: no override in dce80/dce100/dce112/dce120 funcs tables).
Why the bug is certain at source level (path:line)
| # | Fact | Reference |
|---|---|---|
| 1 | Removing a head-pipe plane sets plane_state = NULL, keeps stream |
sys/dev/drm/amd/display/dc/core/dc_resource.c:1462-1467 |
| 2 | Unprivileged KMS user can drive step 1 via atomic plane-disable | sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:5426 (dc_remove_plane_from_context) |
| 3 | Commit tail always calls dc_commit_state for plane-updating commits |
amdgpu_dm.c:4837 with dc_state from dm_state->context (amdgpu_dm.c:4752-4755) |
| 4 | dc_commit_state calls apply_ctx_for_surface per remaining stream (plane_count β₯ 1 while overlay survives) |
core/dc.c:955-959 and :992-997 |
| 5 | dce110_apply_ctx_for_surface walks pipes by stream match only β no plane_state check |
dce110_hw_sequencer.c:2743-2763 |
| 6 | First NULL deref | set_default_colors β dce110_hw_sequencer.c:2100 (pipe_ctx->plane_state->color_space), reached from line 2619 via 2757 |
| 7 | The state is guarded in sibling code (state is real & anticipated) | core/dc.c:1521-1522; dcn10_hw_sequencer.c:2386-2391; core/dc_stream.c:251-256 |
Conclusion: NULL-pointer read at a small constant offset β kernel page fault β panic. Read-only primitive (color_space/format/update_flags reads); no corruption-controlled write β local DoS (Medium), not an escalation path.
Why not reproduced / not false-positive
- not-reproducible-on-guest: QEMU has no AMD display adapter; amdgpu module has nothing to bind. This is a hardware prerequisite, not a code-path liveness issue: every hop of the chain above exists in this tree.
- not-false-positive: the producer (
dc_remove_plane_from_contexthead-pipe detach) and the unguarded consumer are both live code on the same commit path; DCN10's own handler demonstrates the intended guard.
Fix validation
fix.diff authored (guard added in dce110_apply_ctx_for_surface program
loop, mirroring core/dc.c:1521). Kernel rebuild + on-hardware validation
not possible on this guest β fix_status: not_testable.
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.
Fix verification
not_testablenot_testable: no AMD GPU in the guest; fix.diff is a minimal guard identical in shape to the existing dc.c:1521 guard
fix.diff in pack; guest cannot rebuild-test the amdgpu DC path without the hardware
Confirmed kernel references
- sys/dev/drm/amd/display/dc/dce110/dce110_hw_sequencer.c:2743
- sys/dev/drm/amd/display/dc/dce110/dce110_hw_sequencer.c:2100
- sys/dev/drm/amd/display/dc/core/dc_resource.c:1466
- sys/dev/drm/amd/display/dc/core/dc.c:956
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:5426
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:4837
Detail
Exploit chain
DRI user (root:video 0660 node, drm_drv.c:1178+drmP.h:108-110) -> atomic commit: primary+overlay on; then primary off/overlay on (dc_remove_plane_from_context detaches head pipe plane); commit tail dc_commit_state -> apply_ctx_for_surface(stream, plane_count=1) -> program loop processes head pipe with plane_state==NULL -> set_default_colors reads pipe_ctx->plane_state->color_space -> NULL+0xd0 read -> kernel panic.
Evidence (decisive lines)
VERDICT.md table of 7 cited hops; trigger_dce110_headplane.c (3-commit atomic sequence); build.log/run.log (guest compile OK, no DRI adapter); fix.diff (guard mirroring dc.c:1521).
PoC changes
no seed; trigger authored fresh with raw DRM_IOCTL_MODE_ATOMIC structs (no libdrm dependency) so it compiles on the stock guest
Verified recommended fix
Skip pipes with !pipe_ctx->plane_state in dce110_apply_ctx_for_surface's program loop (mirror core/dc.c:1521).
Verdict
Source-certain NULL-deref: dce110_apply_ctx_for_surface (dce110_hw_sequencer.c:2743-2763) walks pipes by stream match without a plane_state NULL check and calls dce110_program_front_end_for_pipe -> set_default_colors (:2100) which dereferences plane_state; the core deliberately produces head pipes with stream!=NULL && plane_state==NULL (dc_resource.c:1462-1467, driven by unprivileged KMS atomic plane-disable via amdgpu_dm.c:5426) and the commit tail unconditionally runs dc_commit_state (amdgpu_dm.c:4837) -> apply_ctx_for_surface (dc.c:955-959). Sibling code guards the exact state (dc.c:1521-1522, dcn10_hw_sequencer.c:2386). Runtime inconclusive: the audit QEMU guest has no AMD GPU, so the amdgpu/DC path cannot execute; trigger compile-tested on the guest (BUILD_RC=0). Expected impact on DCE80/100/110/112/120 hardware: kernel page-fault panic from an unprivileged DRI user (local DoS).
No comments yet.