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

vga_switcheroo_lock_ddc leaks mux_hw_lk on the no-handler / no-switch_ddc error path

  • File: sys/dev/video/vga/vga_switcheroo.c
  • Lines: 557, 558, 559, 560, 561
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:N/A:L
  • CWE: CWE-669 Incorrect Resource Transfer or Closure
  • Confidence: certain

Summary

vga_switcheroo_lock_ddc() acquires mux_hw_lk, then on the early-return path where handler is NULL or handler->switch_ddc is NULL it returns -ENODEV without calling mutex_unlock.

This is a lock leak that will permanently block every subsequent mux_hw_lk acquisition (force_migd, switchto_stage2, other lock_ddc callers) on a different thread, hanging the system's GPU switching and EDID probing.

Root cause

sys/dev/video/vga/vga_switcheroo.c:557 mutex_lock(&vgasr_priv->mux_hw_lk); then immediately at 558–561:

if (!vgasr_priv->handler || !vgasr_priv->handler->switch_ddc) {
    vgasr_priv->old_ddc_owner = -ENODEV;
    return -ENODEV;
}

The return -ENODEV leaves mux_hw_lk held.

The in-tree indexed gmux handler (gmux_handler_indexed at sys/gnu/dev/misc/apple_gmux/apple-gmux.c:600-604) is registered with NO switch_ddc callback, so on indexed (retina) MacBook Pros this exact branch is taken every time drm_get_edid_switcheroo() (sys/dev/drm/drm_edid.c:1770) calls lock_ddc.

The only thing preventing an immediate system-wide hang is that drm_get_edid_switcheroo unconditionally calls vga_switcheroo_unlock_ddc() immediately afterward (drm_edid.c:1772), which (because the lock was held by the same thread and LK_CANRECURSE was set at vga_switcheroo.c:1018) releases it.

But the API contract in the doc at lines 569–583 ("must be called even if vga_switcheroo_lock_ddc() returned an error") is fragile: any future caller that returns early on the -ENODEV error without calling unlock_ddc will deadlock the entire subsystem.

Threat

Local privileged user (root, or any code path inside the kernel that can drive a DRM connector EDID probe on an indexed gmux machine) can trigger the leak; the practical impact today is bounded because the sole caller (drm_get_edid_switcheroo) compensates, but the lock discipline is broken and any new caller (or a regression in drm_edid.c that returns between lock and unlock on error) will hang the GPU subsystem.

Defense-in-depth / kernel robustness issue.

Exploit / PoC

Direct demonstration requires adding a new caller (or modifying drm_edid.c) to take the -ENODEV path and skip unlock.

Concretely: write a small kld module on a machine whose handler is gmux_handler_indexed (no switch_ddc) that calls vga_switcheroo_lock_ddc(some_pdev), checks the -ENODEV return, and deliberately (or via a bug) returns without calling unlock_ddc.

After that, any other thread taking mux_hw_lk (e.g. write 'MIGD' to /dev/vga_switcheroo as root, which hits lines 1184–1187, or i915's vga_switcheroo_force_migd at i915_drv.c:742) blocks forever in mutex_lock β†’ lockmgr(LK_EXCLUSIVE) β†’ tsleep.

Success criterion: the write/MIGD command never returns and the system becomes unresponsive to GPU switching.

Release the lock before returning -ENODEV.

--- a/sys/dev/video/vga/vga_switcheroo.c
+++ b/sys/dev/video/vga/vga_switcheroo.c
@@ -556,7 +556,9 @@ vga_switcheroo_lock_ddc(struct pci_dev *pdev)
    mutex_lock(&vgasr_priv->mux_hw_lk);
    if (!vgasr_priv->handler || !vgasr_priv->handler->switch_ddc) {
        vgasr_priv->old_ddc_owner = -ENODEV;
+       mutex_unlock(&vgasr_priv->mux_hw_lk);
        return -ENODEV;
    }

This matches unlock_ddc's expectation (lines 594–601) and removes the requirement that callers always pair the call.

  • DF-1511 (sibling): NULL-deref in force_migd in same file.
  • DF-1513 (sibling): negative errno convention in same file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1512 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 397 B view raw
VERDICT.md verdict source-confirmation analysis 714 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1512 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: vga_switcheroo.c:557 mutex_lock(mux_hw_lk); 558-561 if (!handler || !handler->switch_ddc) { old_ddc_owner=-ENODEV; return -ENODEV; } LEAVES LOCK HELD. In-tree indexed gmux handler (apple-gmux.c:600-60

Citation: sys/dev/video/vga/vga_switcheroo.c:557-561

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: vga_switcheroo_lock_ddc returns -ENODEV without mutex_unlock (vga_switcheroo.c:558-561)

Verified recommended fix

Source-confirmed: vga_switcheroo_lock_ddc returns -ENODEV without mutex_unlock (vga_switcheroo.c:558-561)

Verdict

Source-confirmed: vga_switcheroo_lock_ddc returns -ENODEV without mutex_unlock (vga_switcheroo.c:558-561)