DF-4311 / verdict.json
{ "finding_id": "DF-4311", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced: kldunload vga_switcheroo.ko succeeds while a dependent module (uaftrig.ko standing in for drm.kos drm_edid.c:1770 call-ins) is loaded, because kern_linker.c records no dependency for globally-resolved cross-module symbols; the next vga_switcheroo_lock_ddc() call faults at the unloaded modules text (Fatal trap 12, IP=fault VA=0xffffffff82601040) and the guest dies. The same MOD_UNLOAD body (shared with MOD_SHUTDOWN) also kfree()s vgasr_priv without checking registered_clients/handler. Trigger requires root (kldunload), so this is a privileged-user UAF/panic (Low), not an unpriv escalation. Fix validated on the same guest: with the teardown guard + declared MODULE_DEPEND, the unload only drops a reference, the module stays resident, and the panic-op returns -19/-19 with the system alive.", "exploit_chain": "root: kldload vga_switcheroo; kldload dependent module holding direct relocation to vga_switcheroo_lock_ddc (drm.ko / i915.ko / amdgpu.ko in-tree, uaftrig.ko in PoC); kldunload vga_switcheroo (module text unmapped + vgasr_priv/vga_switcheroo_buf kfreed); any subsequent DRM switcheroo call-in (EDID probe via drm_get_edid_switcheroo, i915 load-time force_migd, amdgpu lastclose) -> instruction fetch in freed module text -> kernel panic; if the VA range is reused before the call, execution lands in attacker-influenced kernel heap instead.", "evidence": "run.log (baseline phases + unload + panic), panic.txt (Fatal trap 12, instruction pointer == fault VA inside freed module text 0xffffffff82601040), fix.log (patched run: module stays resident, trigger -19/-19, chained clean unload), fix.diff (git apply --check clean against sys/)", "kernel_refs": [ "sys/dev/video/vga/vga_switcheroo.c:1022", "sys/dev/video/vga/vga_switcheroo.c:1024", "sys/dev/video/vga/vga_switcheroo.c:1026", "sys/kern/kern_linker.c:627", "sys/kern/kern_linker.c:1633", "sys/dev/drm/drm_edid.c:1770", "sys/dev/drm/i915/i915_drv.c:742", "sys/dev/drm/amd/amdgpu/amdgpu_kms.c:950" ], "poc_changes": "Authored from scratch (no seed): uaftrig.ko is a minimal dev_ops module that calls vga_switcheroo_lock_ddc/unlock_ddc(NULL) on write, mirroring drm_edid.cs live call pattern; had to add pci_if.h to SRCS and the full DFly include set (sys/bus.h, sys/device.h) to build; MODULE_DEPEND added for the fix-validation build.", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1800, "guest_dirty": 0, "build_cmd": "cd /root/uaftrig && make SYSDIR=/usr/src/sys", "run_cmd": "kldload vga_switcheroo && kldload ./uaftrig.ko && echo trigger > /dev/uaftrig && kldunload vga_switcheroo && echo trigger-after-unload > /dev/uaftrig", "code_hash": "3a9329c59ebef306e65e8ee8cdf57f2d4813614a438b4e6dfa26f68f32b5149e", "notes": "Parser-hunt negative results captured in run.log: all switch commands inert (module can never be active in this tree - all client registration sites compiled out: i915_drv.c:683 #if 0, radeon_device.c:1454 #ifdef DUMBBELL_WIP, amdgpu_device.c:2522 #if 0); 255B/1024B/appended writes bounded (vga_switcheroo_write amt=MIN(resid,255-len) holds); unprivileged write denied (0600). Guest reset clean (with-src) after fix validation.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (teardown guard + MOD_SHUTDOWN split + MODULE_DEPEND declarations in drm/i915/amdgpu) applied to guest /usr/src and validated: pre-fix the trigger panics; post-fix kldunload only drops a reference, module stays resident, trigger returns -19/-19, and the true unload chains after the last dependent unloads. Clean guest afterwards.", "fix_evidence": "findings/poc/DF-4311/fix.log (patched-run console transcript), findings/poc/DF-4311/fix.diff (git apply --check clean against the audit sys/ tree)" } |