β¬’ 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 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 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 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

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)