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

drm_atomic_helper: missing backport of upstream 022debad063e (drm_atomic_state->duplicated) β€” drm_atomic_helper_resume() fails after connector unregistered during suspend, leaving all outputs off

Summary

update_connector_routing() rejects unregistered connectors with -EINVAL without the '!state->duplicated' guard that upstream added in 022debad063e (v5.2); drm_atomic_helper_duplicate_state() never sets such a flag (field absent from this tree's drm_atomic_state). When a connector is hot-unplugged (classic: DP-MST topology change) while the machine is suspended, the resume-time atomic check inside drm_atomic_helper_commit_duplicated_state() fails and the whole display configuration is never restored. Local availability DoS of the display subsystem on real GPU hardware: unplug a DP/MST display while suspended -> resume -> drm_atomic_helper_resume() returns -EINVAL, all outputs (incl. kernel console) stay dark until userspace manages a completely fresh modeset. No memory corruption, no info leak. Reachable via amdgpu and i915 suspend/resume paths shipped in-tree. Runtime PoC impossible on the guest (no DRM device); verification is source-level via direct diff against the upstream fix commit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3791 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.0 KB ↓ raw
VERDICT.md β€” 4.0 KB ↓ raw
fix.diff β€” 1.6 KB view raw
verdict.json β€” 2.9 KB view raw
manifest.json β€” 1.1 KB view raw
env.txt β€” 867 B view raw

DF-3791 β€” Missing upstream fix 022debad063e ("drm/atomic: Add drm_atomic_state->duplicated")

DragonFlyBSD sys/dev/drm/drm_atomic_helper.c (β‰ˆ Linux v5.0-rc vintage) does not carry upstream commit 022debad063e (torvalds, 2019-02-05, first released in v5.2-rc1). That commit:

  1. adds a duplicated flag to struct drm_atomic_state,
  2. sets it in drm_atomic_helper_duplicate_state(),
  3. gates the "unregistered connector" rejection in update_connector_routing() with !state->duplicated.

Without it, drm_atomic_helper_resume() fails its atomic check whenever any connector that was part of the suspended configuration has been unregistered (hot-unplug / DP-MST topology change) while the machine was suspended, and the entire display configuration fails to restore (all outputs stay dark, kernel console dead until userspace manages a fresh modeset).

Local evidence

  • sys/dev/drm/drm_atomic_helper.c:325 β€” the guard is missing: c if (drm_connector_is_unregistered(connector) && crtc_state->active) { ... return -EINVAL; upstream v5.2+: if (!state->duplicated && drm_connector_is_unregistered(...) && ...)
  • sys/dev/drm/drm_atomic_helper.c:3840-3901 β€” drm_atomic_helper_duplicate_state() never sets a duplicated flag (the field does not exist anywhere in sys/dev/drm/include/drm/).
  • Resume path that then dies: drm_atomic_helper_commit_duplicated_state() (:3230) β†’ drm_atomic_commit() β†’ drm_atomic_helper_check_modeset() β†’ update_connector_routing() (:325 returns -EINVAL).
  • In-tree users of the broken suspend/resume pair: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:710 / :908 (drm_atomic_helper_suspend / drm_atomic_helper_resume) and sys/dev/drm/i915/intel_display.c:6381 (i915 suspend).

What upstream says (commit 022debad063e)

Since commit 39b50c603878 ("drm/atomic_helper: Stop modesets on unregistered connectors harder") we've been failing atomic checks if they try to enable new displays on unregistered connectors. This is fine except for the one situation that breaks atomic assumptions: suspend/resume. If a connector is unregistered before we attempt to restore the atomic state, we end up failing the atomic check that happens when trying to restore the state during resume.

Reproduce (requires real GPU hardware β€” NOT possible on this guest)

  1. Boot DFly with amdgpu or i915 KMS on a machine with a hot-pluggable display (DP or DP-MST dock).
  2. acpiconf -s 4 (suspend to RAM) with the display configured.
  3. Unplug the display / dock while suspended.
  4. Resume. Expected on vulnerable kernel: drm_atomic_helper_resume() fails with -EINVAL ("[CONNECTOR:n] is not registered"), outputs never re-enable. Expected with fix applied: resume restores the remaining outputs.

The provided QEMU guest has no DRM-capable GPU (QEMU std VGA 0x1111:1234, no in-tree driver, drm.ko not loaded, /dev/dri absent) β€” see env.txt. Runtime verification is therefore impossible on this guest; the finding is proven at source level by direct diff against the upstream fix.

VERDICT.md
↓ download raw

DF-3791 VERDICT

Status: inconclusive (runtime verification impossible on this guest β€” no DRM device exists; source-level divergence is certain). Source-level severity: Low (availability / local DoS of the display subsystem; no memory corruption, no info leak, no privilege impact).

What was verified

1. The bug is real in this tree (certain)

This file (sys/dev/drm/drm_atomic_helper.c, 4021 LOC) is byte-comparable to upstream Linux ~v5.0-rc1 (drm-misc window Nov 2018 – Jan 2019) except for a small set of deltas. Two independent checks establish the vintage: pick_single_encoder_for_connector / max_requested_bpc / damage-helper (v5.0-final features) are absent, while __drm_atomic_helper_set_config, fake_vblank, commit_cleanup_done and the 2216322919c8 async-fb block (pre-v5.0-final features) are present.

Upstream commit 022debad063e "drm/atomic: Add drm_atomic_state->duplicated" (merged for v5.2) is NOT carried:

  • local update_connector_routing() rejects duplicated states on unregistered connectors β€” sys/dev/drm/drm_atomic_helper.c:325-329, upstream adds !state->duplicated && here;
  • local drm_atomic_helper_duplicate_state() β€” sys/dev/drm/drm_atomic_helper.c:3840-3901 β€” sets no such flag; the field drm_atomic_state.duplicated does not exist anywhere under sys/dev/drm/include/ (rg -n duplicated β†’ no hits).

Upstream added that flag specifically because resume-through-duplicated-state must ignore connector unregistration; without it the atomic check run inside drm_atomic_commit() (invoked from drm_atomic_helper_commit_duplicated_state, sys/dev/drm/drm_atomic_helper.c:3252) returns -EINVAL and the whole resume update is aborted β€” nothing is swapped in, no outputs are programmed.

2. Reachability in-tree (likely, hardware-dependent)

drm_atomic_helper_suspend/resume are used by the two KMS drivers DFly actually ships for real hardware:

Trigger: suspend with outputs configured β†’ connector unregistered while suspended (DP cable yanked, USB-C/DP dock removed, DP-MST topology vanished β€” MST is the classic case the upstream commit describes) β†’ resume. Result: [CONNECTOR:n:...] is not registered β†’ -EINVAL β†’ displays stay off; the kernel console is unusable until userspace performs a fresh modeset. Impact ceiling: local availability (display DoS) β€” recoverable, no memory corruption.

3. Why runtime verification is impossible here (inconclusive)

Guest survey (env.txt):

  • ls /dev/dri β†’ No such file or directory; ls /dev/drm* β†’ none.
  • pciconf -l β†’ only vgapci0@pci0:0:2.0 chip=0x11111234 (QEMU std VGA). The DFly tree contains no virtio_gpu driver (sys/dev/virtual/virtio/{balloon,block,net,pci,random,scsi} only) and no driver for the emulated std VGA, so no DRM device can exist on this guest.
  • kldstat β†’ drm.ko not loaded (modules exist in /boot/kernel but nothing can attach).

Both findings of this audit are therefore proven by source diff against the upstream fix, not by guest execution. No panic/log sample exists; fix.diff is provided un-applied per audit rules.

Exploit chain

None β€” not a memory-corruption class. Availability-only.

Backport upstream commit 022debad063e (add duplicated to struct drm_atomic_state, set it in drm_atomic_helper_duplicate_state, gate the check in update_connector_routing). See fix.diff in this pack.

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

No DRM device on the guest, so no kernel could be built/run against the buggy or patched path; fix is a straight backport of the upstream-accepted commit 022debad063e.

findings/poc/DF-3791/fix.diff (git-apply-able vs this tree, authored post-verification, never applied to sys/).
↓ fix.diffper-fix-DF-3791

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (diff-vs-upstream proof); README.md (trigger narrative); sys/dev/drm/drm_atomic_helper.c:325-329 (missing guard), :3840-3901 (duplicate_state sets no flag), :3230-3258 (commit_duplicated_state -> drm_atomic_commit -> check); amdgpu_dm.c:710/:908 and intel_display.c:6381 (suspend/resume users); env.txt (guest GPU/module survey)

PoC changes

No PoC source produced: trigger requires physical GPU hardware with hot-pluggable displays (DP/MST). Guest is virtio-only with no DRM driver; verification is source-level (upstream fix commit diff).

Verified recommended fix

Backport upstream 022debad063e: add drm_atomic_state.duplicated, set it in drm_atomic_helper_duplicate_state(), and gate the unregistered-connector check in update_connector_routing() (fix.diff in this pack).

Verdict

Source-level certain, runtime-inconclusive: sys/dev/drm/drm_atomic_helper.c:325 lacks the '!state->duplicated' guard added by upstream commit 022debad063e (v5.2), and the duplicated flag does not exist in this tree's drm_atomic_state at all; drm_atomic_helper_resume() (used by amdgpu_dm.c:908 and i915) therefore fails with -EINVAL whenever a connector is unregistered during suspend, leaving all outputs off after resume (availability only, no memory corruption). The QEMU guest has no DRM-capable GPU (QEMU std VGA 0x1111:1234, no virtio_gpu/std-VGA driver in tree, drm.ko unloaded, /dev/dri absent), so the trigger cannot be executed on this guest.