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.
Recommended fix
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.
Related findings
- DF-1511 (sibling): NULL-deref in
force_migdin same file. - DF-1513 (sibling): negative errno convention in same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1512 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedfix.diff compiled in batch kernel build rc=0 -Werror
fix.diff compiled in batch kernel build rc=0 -Werror
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)
No comments yet.