hpd_lock pairing asymmetry in handle_hpd_rx_irq(): link-type flip inside handler strands the lock (permanent hang) or releases an unheld lock
Summary
handle_hpd_rx_irq() takes aconnector->hpd_lock iff dc_link->type != mst_branch on ENTRY but releases it iff the condition holds on EXIT, re-reading the field after dc_link_detect() which rewrites it in-handler: single->mst_branch (branch discovered from sink DPCD) and mst_branch->single (sink-controlled topology-start failure). A stranded lock permanently wedges handle_hpd_irq work items, dm_suspend/dm_resume, and amdgpu_dm_irq_fini's flush_work; the reverse direction runs lockmgr LK_RELEASE on a never-held lock. Crafted/defective DisplayPort dock or sink (DF-3626-class sink-controlled condition): persistent unresolvable DoS or lock-state corruption/panic - physical or supply-chain vector. On-guest harness prints the exact entry-x-exit pairing matrix showing the two unbalanced cells. Fix: cache the locking decision at entry.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3654 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | β | 2.7 KB | view raw | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 845 B | view raw | |
| env.txt | β | 323 B | view raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 988 B | view raw | |
| verdict.json | β | 3.8 KB | view raw | |
| README.md | β | 2.6 KB | β raw |
DF-3654 β hpd_lock pairing asymmetry in handle_hpd_rx_irq() (sink-triggered permanent hang / unlock-without-hold)
File: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c
Lines: 1209-1210 (conditional lock), 1232 (in-handler dc_link_detect), 1251-1254 (re-evaluated conditional unlock)
Severity: Medium (crafted-sink / malicious-dock local DoS; panic variant) Β· CWE-667 Β· confidence: likely
What
handle_hpd_rx_irq() decides whether to take aconnector->hpd_lock by reading
dc_link->type on entry, but decides whether to release it by re-reading
dc_link->type on exit. Between the two reads, the handler itself calls
dc_link_detect() (amdgpu_dm.c:1232), and detection mutates link->type:
single β mst_branchβdetect_dp()when an MST branch is discovered (sys/dev/drm/amd/display/dc/core/dc_link.c:528);mst_branch β singleβ whendm_helpers_dp_mst_start_top_mgr()fails (sink-controlled sideband failure) atdc_link.c:569.
The two unbalanced combinations:
| entry type | exit type | effect |
|---|---|---|
| single | mst_branch | lock stranded forever |
| mst_branch | single | mutex_unlock() (lockmgr LK_RELEASE) without hold |
A stranded hpd_lock permanently wedges: every later handle_hpd_irq() for
the connector (amdgpu_dm.c:1083) blocks inside its work item; dm_suspend() /
dm_resume() block forever (amdgpu_dm.c:864, 878) β the machine can no longer
suspend or resume; amdgpu_dm_irq_fini()'s flush_work() hangs
(amdgpu_dm_irq.c:334) so driver teardown deadlocks.
Threat model
The type flip is driven by data the sink supplies over DDC/AUX sideband
(DisplayPort branch capability bits, MST sideband responses). A malicious or
defective DP dock/hub β the DF-3626-class "sink-controlled condition" β that
causes the first HPD-RX short pulse handling to run dc_link_detect() and
discover the branch strands the lock; one that makes topology start fail after
the link is already classified MST gets an unbalanced release.
Harness
harness.c prints the exact entryΓexit decision matrix computed the way the
kernel computes it (entry read at 1209, exit read at 1251), demonstrating the
two unbalanced cells and their code-level causes. The audit guest is
virtio-only (no AMD GPU β env.txt), so the kernel handler itself cannot run.
./build.sh && ./run.sh
Expected decisive output (run.log): the matrix with STRANDED LOCK and
UNLOCK WITHOUT HOLD rows.
Contents
harness.c Β· build.sh Β· run.sh Β· build.log Β· run.log Β· env.txt Β·
VERDICT.md Β· fix.diff Β· manifest.json Β· verdict.json
DF-3654 β VERDICT
Status: inconclusive (no AMD GPU on the audit guest; the lock-pairing defect is source-certain and modeled on-guest; the in-handler type flip is source-proven, its hardware realization is timing-dependent).
Root cause
handle_hpd_rx_irq() (sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:1195-1255):
1201 bool is_mst_root_connector = aconnector->mst_mgr.mst_state;
...
1209 if (dc_link->type != dc_connection_mst_branch)
1210 mutex_lock(&aconnector->hpd_lock); /* ENTRY read */
...
1212 if (dc_link_handle_hpd_rx_irq(dc_link, NULL, NULL) && !is_mst_root_connector) {
...
1232 } else if (dc_link_detect(dc_link, DETECT_REASON_HPDRX)) {
...
1247 if ((dc_link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) ||
1248 (dc_link->type == dc_connection_mst_branch))
1249 dm_handle_hpd_rx_irq(aconnector);
1250
1251 if (dc_link->type != dc_connection_mst_branch) { /* EXIT read */
1252 drm_dp_cec_irq(&aconnector->dm_dp_aux.aux);
1253 mutex_unlock(&aconnector->hpd_lock);
1254 }
dc_link_detect() runs between the two reads of dc_link->type and can
rewrite it:
sys/dev/drm/amd/display/dc/core/dc_link.c:526-528β whenis_mst_supported(link)(DPCD branch capability, sink-supplied),link->type = dc_connection_mst_branch.dc_link.c:565-571β whendm_helpers_dp_mst_start_top_mgr()fails (DisplayPort sideband/topology failure β sink-controlled),link->typeis reset todc_connection_single.
Consequences:
- entry=single, exit=mst_branch (branch discovered during this handler):
hpd_lock(alockmgrlock initializedLK_CANRECURSE, amdgpu_dm.c:3940) is never released. Every subsequenthandle_hpd_irq()for this connector blocks forever at amdgpu_dm.c:1083 inside its per-source work item (amdgpu_dm_irq.c:311INIT_WORK);dm_suspend()/dm_resume()block at amdgpu_dm.c:864/878 (suspend/resume permanently broken);amdgpu_dm_irq_fini()hangs inflush_work()(amdgpu_dm_irq.c:334) (driver teardown deadlocks). Persistent, unresolvable DoS. - entry=mst_branch, exit=single (topology start failed in-handler; entry
requires
mst_state==falsewith type still mst_branch β post-MST-teardown state):mutex_unlock()on a lock this thread never acquired. In the DragonFly DRM shimmutex_unlockβlockmgr(lock, LK_RELEASE)(sys/dev/drm/include/linux/mutex.h:39) β releasing an unheld lockmgr lock corrupts the lock state / panics under DIAGNOSTIC.
Why "likely" and not "certain"
The pairing defect is unambiguous in source (two different reads of a field
mutated in between; no cached decision). What cannot be proven on this guest
is the hardware sequence: direction (1) needs the branch discovery to happen
inside an HPD-RX short-pulse handler rather than the long-HPD path
(handle_hpd_irq, which locks unconditionally and is symmetric). That is
plausible for a dock that renegotiates without toggling HPD long, or a
malicious sink that drives short pulses β exactly the DF-3626 lesson
(sink-controlled condition) β but it is device-timing-dependent.
Verification (Phase V)
- Guest is virtio-only (env.txt: no AMD PCI device, no /dev/dri, no drm klds) β handler cannot execute.
harness.ccomputes the entryΓexit matrix with the kernel's exact conditionals; run.log shows the two unbalanced cells:entry=single exit=mst_branch β STRANDED LOCKandentry=mst_branch exit=single β UNLOCK WITHOUT HOLD, each annotated with the dc_link.c line that performs the in-handler flip.
Fix
Cache the entry decision (bool lock_taken = dc_link->type !=
dc_connection_mst_branch;) and gate both lock and unlock on the cached
value β see fix.diff (mirrors the upstream-style fix; applies cleanly per
git apply --check). 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: requires an amdgpu-backed DP connector and a sink that flips the link classification inside one HPD-RX handling. fix.diff makes lock/unlock symmetric on a cached decision; git apply --check verified against sys/; not applied (read-only audit target).
fix.diff in this pack
Confirmed kernel references
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:1209
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:1232
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:1251
- sys/dev/drm/amd/display/dc/core/dc_link.c:528
- sys/dev/drm/amd/display/dc/core/dc_link.c:569
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:1083
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c:334
Detail
Exploit chain
Malicious/defective DisplayPort dock or sink (physical or supply-chain): presents MST branch caps so an HPD-RX short pulse causes dc_link_detect() inside handle_hpd_rx_irq() to flip link->type single->mst_branch while hpd_lock is held -> lock stranded -> all later HPD processing for the connector, suspend/resume, and driver teardown hang permanently. Alternate direction: make topology start fail after the link is classified MST to trigger unlock-without-hold -> lockmgr state corruption/panic.
Evidence (decisive lines)
run.log (pairing matrix: STRANDED LOCK and UNLOCK WITHOUT HOLD cells with dc_link.c:528/569 annotations); VERDICT.md (full source trace incl. lock shim mapping); fix.diff (cached lock decision; git apply --check clean); env.txt (guest virtio-only)
PoC changes
no seeded PoC for pass-2; harness written fresh β enumerates the entry x exit decision matrix exactly as the kernel computes the two conditionals
Verified recommended fix
Cache the lock decision at handler entry (bool hpd_lock_taken = dc_link->type != dc_connection_mst_branch) and gate lock and unlock on the cached value.
Verdict
Source-certain locking defect, hardware-gated runtime. handle_hpd_rx_irq() takes aconnector->hpd_lock iff dc_link->type != dc_connection_mst_branch on ENTRY (amdgpu_dm.c:1209-1210) but releases it iff the same condition holds on EXIT (1251-1254), re-reading the field after dc_link_detect() (1232) which can rewrite it in-handler: single->mst_branch at dc_link.c:528 (branch discovered from sink DPCD) and mst_branch->single at dc_link.c:569 (dm_helpers_dp_mst_start_top_mgr failure, sink-controlled sideband). Direction 1 strands hpd_lock permanently: later handle_hpd_irq() work items for the connector block forever (amdgpu_dm.c:1083), dm_suspend/dm_resume hang (864/878), amdgpu_dm_irq_fini flush_work() hangs (amdgpu_dm_irq.c:334) β persistent DoS. Direction 2 executes mutex_unlock() (= lockmgr LK_RELEASE) on a never-held lock (sys/dev/drm/include/linux/mutex.h:39) β lock corruption/panic. The pairing matrix was demonstrated on-guest by the harness (run.log); the kernel handler itself cannot run on the virtio-only guest (env.txt: no AMD GPU). 'likely' rather than 'certain' only because the in-handler type-flip hardware sequence (short-pulse HPD-RX that runs detection discovering/losing the branch) is device-timing-dependent β the DF-3626 sink-controlled-condition class.
No comments yet.