DF-5467 / verdict.json
{ "finding_id": "DF-5467", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "vga_pci_unmap_bios() re-evaluates vga_pci_is_boot_display() (vga_pci.c:288) to decide which mapping kind to tear down, but vga_pci_map_bios() itself mutates that predicates inputs (it sets PCIB_BCR_VGA_ENABLE on the parent bridge, vga_pci.c:197-201, and the predicate latches the sticky vga_pci_default_unit, vga_pci.c:139). When the answer flips between map and unmap \u2014 the stock case being a secondary GPU behind a bridge with VGA-enable clear and no boot display selected, driven by drms radeon_read_bios() map/unmap pair \u2014 unmap executes the shadow-copy teardown pmap_unmapdev(bios, 128K) (vga_pci.c:290) on a ROM-BAR mapping of size rman_get_size() (64K here). pmap_unmapdev clears PTEs and frees kernel_map VA for the full 128K (pmap.c:6223-6232): kernel page-table teardown of up to 64K of neighboring kernel mappings plus a kernel_map VA double-free of unowned space; the KASSERTs at vga_pci.c:314-316 are bypassed by the early return, and the ROM resource leaks so the next map_bios returns stale unmapped kva. Demonstrated on the QEMU guest: CONTROL phases (consistent predicate) clean; BUG phase ends in Fatal trap 12, supervisor read data page not present, at the ROM kva. fix.diff (record the mapping kind in the softc) rebuilt and installed on the guest: identical PoC completes cleanly (fresh remap reads 0x55/0xaa, guest stays up).", "exploit_chain": "firmware/hardware state (bridge VGA-enable clear + default_unit unset + GPU decoding on, or malicious PCIe device presenting this topology) -> drm attach (radeon_read_bios, radeon_bios.c:86-104) -> vga_pci_map_bios ROM path sets the bridge bit -> vga_pci_unmap_bios re-evaluates predicate, now true -> pmap_unmapdev(rom_kva, 128K) over a 64K mapping -> (a) PTEs of the ROM mapping and up to 16 adjacent kernel pages cleared (demonstrated: kernel page fault/panic), (b) kmem_free of 64K of kernel_map VA the kernel still considers allocated (VA double-assignment primitive), (c) ROM resource leaked with live vr_res -> subsequent map_bios hands out stale kva -> fault in kernel poll loop. Weaponization to uid=0 would require grooming kernel_map adjacency so the freed 64K range re-allocates over a victim object; not executed (trigger is firmware-state-gated rather than unpriv-user-gated).", "evidence": "panic.txt (Fatal trap 12, fault virtual address = ROM kva 0xfffff8011887c000, supervisor read data, page not present, stopped at df5467_modevent+0x3ff); serial-full.log lines 30-46 (full CONTROL/BUG sequence; CONTROL phases prove balanced map/unmap when the predicate does not flip); fix_run.log (patched kernel: post-bug remap -> 0xfffff801185a4000, remap reads 0x55/0xaa = 55/aa (clean), kldload rc=0, guest alive); fix.diff; fix_build.log; VERDICT.md", "kernel_refs": [ "sys/bus/pci/vga_pci.c:175", "sys/bus/pci/vga_pci.c:197", "sys/bus/pci/vga_pci.c:200", "sys/bus/pci/vga_pci.c:288", "sys/bus/pci/vga_pci.c:290", "sys/bus/pci/vga_pci.c:314", "sys/bus/pci/vga_pci.c:139", "sys/platform/pc64/x86_64/pmap.c:6230", "sys/platform/pc64/x86_64/pmap.c:6231", "sys/dev/drm/radeon/radeon_bios.c:86" ], "poc_changes": "run_root does not forward stdin (first push produced an empty .c and a vacuous green build - switched to scp); KLD Makefile needed device_if.h/bus_if.h/pci_if.h in SRCS; DF kernel env accessor is kgetenv not getenv; seeded ROM rle size math corrected to 32-bit ~mask+1 and stale rle deleted before re-add; ROM resource-list seeding added after discovering live that DFs lazy pci_alloc_map() cannot allocate ROM BARs at all on this guest (filed as DF-5468) - the seed emulates FreeBSD-style pre-reservation/bit0-masking hardware; CONTROL phases and a post-bug remap discriminator added because a correct ROM-path release also unmaps the ROM (touch-after-unmap alone cannot distinguish fixed from unfixed).", "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 (baseline); fix kernel #1: Tue Oct 6 00:18:55 UTC 2026 same tree + fix.diff", "runtime_sec": 1180, "guest_dirty": 0, "build_cmd": "scp -F dfbsd-qemu/config df5467.c dfbsd:/tmp/df5467/ ; vm.sh run_root printf \"KMOD=\\tdf5467\\nSRCS=\\tdf5467.c device_if.h bus_if.h pci_if.h\\n\\n.include <bsd.kmod.mk>\\n\" > /tmp/df5467/Makefile && cd /tmp/df5467 && make", "run_cmd": "vm.sh run_root kldload /tmp/df5467/df5467.ko (baseline: panic; fix kernel with same binary: clean, see fix_run.log)", "code_hash": "a40b9630ee7970e5d0384a39882e01cbb4378bc1667a45d530747b212f976887", "notes": "Impact panic is the demonstrated ceiling on this guest; the underlying primitive is stronger (kernel_map VA double-assignment + up-to-64K foreign PTE teardown) - see VERDICT.md Exploitation ceiling. Guest has no PCI-PCI bridge above the VGA, so the predicate flip is performed by the kernels own vga_pci_is_boot_display() latch (identical semantics to the bridge-bit self-flip map_bios causes on real hardware); CONTROL phases isolate the flip as the only variable. Guest reset to clean-source snapshot after the run (guest_dirty=0). Related finding DF-5468: on guests/hardware whose ROM BAR readback preserves the enable bit, DFs lazy pci_alloc_map() fails the ROM allocation first, masking this chain until the ROM BAR is pre-reserved.", "recommended_fix": "Record the mapping kind in struct vga_pci_softc (vga_bios_shadow) when vga_pci_map_bios() creates it, and have vga_pci_unmap_bios() tear down that recorded kind instead of re-evaluating vga_pci_is_boot_display(); full diff in findings/poc/DF-5467/fix.diff", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to the guest /usr/src copy, kernel rebuilt with make -j6 nativekernel KERNCONF=X86_64_GENERIC (rc=0, -Werror), installed, rebooted (#1: Tue Oct 6 00:18:55 UTC 2026). Identical PoC and state sequence: BUG-phase unmap now performs a proper ROM-path release; post-bug remap allocates a fresh mapping and reads the ROM signature 0x55/0xaa; unmap clean; kldload rc=0; guest alive. Baseline (stock #0 kernel): Fatal trap 12 kernel page fault, guest down.", "fix_evidence": "fix_run.log (patched run, decisive lines post-bug remap -> 0xfffff801185a4000, remap reads 0x55/0xaa = 55/aa (clean), alive); panic.txt (baseline); fix_build.log (fixed-tree build completion rc=0); fix.diff (git apply --check clean against the repo tree)", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Tue Oct 6 00:18:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64" } |