NULL dereference via WARN_ON(!status->plane_count) immediately after WARN_ON(!status) in amdgpu_dm_atomic_commit_tail()
Summary
status = dc_stream_get_status(stream); WARN_ON(!status); WARN_ON(!status->plane_count); - WARN_ON reports but does not stop control flow, so a NULL status (stream absent from the DC current context, e.g. right after the tolerated dc_commit_state() failure) is dereferenced, reading offsetof(dc_stream_status, plane_count)=8 -> page fault -> panic. Local DoS via a DRM-master underscan-only commit on a CRTC whose stream lacks DC status - two-step trigger, hence Low. On-guest harness with verbatim struct layout prints WARN_ON(!status)=1 then SIGSEGVs at offset 8.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3655 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | β | 1.6 KB | view raw | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 139 B | view raw | |
| env.txt | β | 323 B | view raw | |
| VERDICT.md | β | 2.6 KB | β raw | |
| fix.diff | β | 462 B | view raw | |
| manifest.json | β | 816 B | view raw | |
| verdict.json | β | 3.0 KB | view raw | |
| README.md | β | 1.9 KB | β raw |
DF-3655 β NULL dereference via WARN_ON(!status->plane_count) after WARN_ON(!status) in amdgpu_dm_atomic_commit_tail()
File: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c
Lines: 4888-4890
Severity: Low (conditional kernel panic β requires a stream without DC status in the underscan path) Β· CWE-476 Β· confidence: certain (code defect unambiguous)
What
4888 status = dc_stream_get_status(dm_new_crtc_state->stream);
4889 WARN_ON(!status);
4890 WARN_ON(!status->plane_count); /* <-- dereferences `status` */
WARN_ON() reports but does not stop execution. If dc_stream_get_status()
returns NULL (the stream is not present in the DC current context β e.g.
immediately after a failed dc_commit_state(), which itself is only WARN'd at
amdgpu_dm.c:4837 while execution continues), line 4890 dereferences the NULL
pointer at offset 8 (offsetof(struct dc_stream_status, plane_count):
primary_otg_inst + stream_enc_inst, dc/dc_stream.h) β page fault β panic.
Reachability is two-step: (a) an underscan/scaling-only commit on a connector whose CRTC has a live stream (the normal committed display case), combined with (b) the stream being absent from the current DC context (commit failure or teardown race). Because (b) is an internal DC failure condition rather than a purely attacker-chosen state, severity is Low.
Harness
harness.c reproduces the exact evaluation order with a verbatim-layout
dc_stream_status model; the guest is virtio-only (env.txt) so the kernel
path itself cannot run.
./build.sh && ./run.sh
Expected decisive output (run.log):
WARN_ON(!status) = 1 ... <- printed, execution continues (kernel WARN) Segmentation fault <- status->plane_count deref at offset 8 EXIT=139
Contents
harness.c Β· build.sh Β· run.sh Β· build.log Β· run.log Β· env.txt Β·
VERDICT.md Β· fix.diff Β· manifest.json Β· verdict.json
DF-3655 β VERDICT
Status: inconclusive (kernel path hardware-gated on the virtio-only guest); the evaluation-order defect is source-certain and the NULL deref is modeled on-guest at the exact struct offset.
Root cause
amdgpu_dm_atomic_commit_tail() connector-scaling loop
(sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c):
4888 status = dc_stream_get_status(dm_new_crtc_state->stream);
4889 WARN_ON(!status);
4890 WARN_ON(!status->plane_count);
WARN_ON(x) expands to a report + unlikely(x); it does not terminate
the control flow. When dc_stream_get_status() returns NULL, line 4889
prints the warning and line 4890 immediately loads
*(int *)((char *)NULL + offsetof(struct dc_stream_status, plane_count)).
struct dc_stream_status begins with int primary_otg_inst; int
stream_enc_inst; int plane_count; (sys/dev/drm/amd/display/dc/dc_stream.h)
β offset 8 β unmapped page-0 VA β page fault β panic.
Note the adjacent line 4837: WARN_ON(!dc_commit_state(dm->dc, dc_state)); β
a failed DC commit is explicitly tolerated (warned, execution continues with
dc_state not applied), which is precisely the condition that leaves a
stream absent from the current context so that dc_stream_get_status()
returns NULL on the next underscan-path visit of this loop.
Reachability
Requires the loop to be entered (connector attached to a CRTC, no modeset,
scaling state changed β same entry conditions as DF-3653's block, but with a
non-NULL stream) and dc_stream_get_status(stream) returning NULL.
The first condition is fully userspace-chosen (DRM-master atomic commit);
the second depends on DC internal state (failed commit / teardown race),
hence Low severity / certain code defect, likely trigger.
Verification (Phase V)
- Guest virtio-only (env.txt): no AMD GPU, driver cannot attach β kernel path not executable.
- Harness models the exact evaluation order with a verbatim-field-order
dc_stream_status; on-guest it printsWARN_ON(!status) = 1(the kernel WARN fires) and then SIGSEGVs at offset 8 (run.log, EXIT=139).
Fix
Treat a NULL status as an error and skip (consistent with the intent of the
WARN) β see fix.diff: replaces the two WARN_ONs with a DC_ERR + continue.
git apply --check clean. fix_status: not_testable (hardware-gated).
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_testableHardware-gated (needs amdgpu display + DC commit failure to seed status==NULL). fix.diff converts the first WARN_ON into an early continue; git apply --check verified against sys/; not applied (read-only audit target).
fix.diff in this pack
Confirmed kernel references
Detail
Exploit chain
DRM-master atomic commit that changes only underscan properties on a connector whose CRTC holds a stream that is not present in the DC current context (after a warned-but-tolerated dc_commit_state failure) -> WARN_ON(!status) fires without effect -> status->plane_count loads from NULL+8 -> kernel panic (local DoS).
Evidence (decisive lines)
run.log (WARN_ON printed, then SIGSEGV EXIT=139 at offset 8); VERDICT.md (evaluation-order trace + 4837 linkage); fix.diff (NULL status -> DC_ERR + continue; git apply --check clean); env.txt
PoC changes
no seeded PoC for pass-2; harness written fresh β WARN_ON macro modeled with printf+fflush so the pre-fault warning is captured (kernel WARN prints before the fault)
Verified recommended fix
Replace the WARN_ON(!status); WARN_ON(!status->plane_count); pair with an explicit if (!status) { DC_ERR(...); continue; } before any dereference.
Verdict
Source-certain evaluation-order defect, hardware-gated runtime. amdgpu_dm_atomic_commit_tail() at amdgpu_dm.c:4888-4890 does status = dc_stream_get_status(stream); WARN_ON(!status); WARN_ON(!status->plane_count); β WARN_ON reports but does not stop control flow, so a NULL status (stream absent from the current DC context, e.g. right after the tolerated dc_commit_state() failure warned at amdgpu_dm.c:4837) is dereferenced at line 4890, reading offsetof(struct dc_stream_status, plane_count) = 8 β page fault β panic. Trigger is two-step: userspace-chosen loop entry (underscan-only atomic commit on a live CRTC, DRM master) plus an internal DC condition making the status lookup fail, hence Low severity. On-guest harness with the verbatim struct layout prints WARN_ON(!status)=1 then SIGSEGVs at offset 8 (run.log EXIT=139); the kernel path itself cannot run β guest is virtio-only with no AMD GPU (env.txt).
No comments yet.