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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3655 Β· 11 files
FileTypeDescriptionSize
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

VERDICT.md
↓ download raw

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 prints WARN_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_testable
baseline no→ patch + rebuild →patched clean

Hardware-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
↓ fix.diffper-fix-DF-3655

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).