# 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):

```c
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` — when `is_mst_supported(link)`
  (DPCD branch capability, sink-supplied), `link->type = dc_connection_mst_branch`.
* `dc_link.c:565-571` — when `dm_helpers_dp_mst_start_top_mgr()` fails
  (DisplayPort sideband/topology failure — sink-controlled), `link->type` is
  reset to `dc_connection_single`.

Consequences:

1. **entry=single, exit=mst_branch** (branch discovered during this handler):
   `hpd_lock` (a `lockmgr` lock initialized `LK_CANRECURSE`,
   amdgpu_dm.c:3940) is never released. Every subsequent
   `handle_hpd_irq()` for this connector blocks forever at
   amdgpu_dm.c:1083 inside its per-source work item
   (`amdgpu_dm_irq.c:311` INIT_WORK); `dm_suspend()`/`dm_resume()` block at
   amdgpu_dm.c:864/878 (suspend/resume permanently broken);
   `amdgpu_dm_irq_fini()` hangs in `flush_work()` (amdgpu_dm_irq.c:334)
   (driver teardown deadlocks). Persistent, unresolvable DoS.
2. **entry=mst_branch, exit=single** (topology start failed in-handler; entry
   requires `mst_state==false` with type still mst_branch — post-MST-teardown
   state): `mutex_unlock()` on a lock this thread never acquired. In the
   DragonFly DRM shim `mutex_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.c` computes the entry×exit matrix with the kernel's exact
  conditionals; run.log shows the two unbalanced cells:
  `entry=single exit=mst_branch → STRANDED LOCK` and
  `entry=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.
