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)
PoC verification
Evidence pack
findings/poc/DF-3791 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
- adds a
duplicatedflag tostruct drm_atomic_state, - sets it in
drm_atomic_helper_duplicate_state(), - 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 insys/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()(:325returns -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) andsys/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)
- Boot DFly with amdgpu or i915 KMS on a machine with a hot-pluggable display (DP or DP-MST dock).
acpiconf -s 4(suspend to RAM) with the display configured.- Unplug the display / dock while suspended.
- 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.
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 fielddrm_atomic_state.duplicateddoes not exist anywhere undersys/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:
- amdgpu (DC):
sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:710(adev->dm.cached_state = drm_atomic_helper_suspend(adev->ddev)) and:908(drm_atomic_helper_resume(ddev, dm->cached_state)). - i915:
sys/dev/drm/i915/intel_display.c:6381.
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β onlyvgapci0@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.
Recommended fix
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_testableNo 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/).
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.
No comments yet.