DF-4106 / verdict.json
{ "finding_id": "DF-4106", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on the audit guest through the RUNNING KERNELS OWN vge_rxeof()/vge_newbuf() (static symbols of the built-in driver, resolved via nm and dispatched from a KLD rig containing zero driver code). With the production failure condition imposed (cluster pool drained until m_getcl(M_NOWAIT) fails - the kernel itself logs objcache(mbuf pkthdr+cl) exhausted), the ignored vge_newbuf() failure at if_vge.c:1294 produces: (1) DOUBLE-REFERENCE - the head chain and vge_rx_mbuf[0..1] hold the same mbufs; (2) on ring wrap the chain head is queued into its own tail - CYCLIC/double-queued chain; (3) the verbatim vge_stop tail frees m0/m1 twice (head chain + RX-list loop); (4) with a completed jumbo, the EOF branch frees the chain while the ring dangles, and the next walk WRITES through the dangling entries and DELIVERS the freed mbufs to ifp->if_input; (5) alias scans prove one mbuf returned to two owners (allocations #1244/#1246 and #1184/#1246 SAME pointer); (6) the panic variant steers the aliased object (owner A header write) so owner Bs normal m_freem() chases m_next=0x4141414141414141 -> Fatal trap 9 at m_free+0x10 (movswq 0x20(%rdi),%rax) - kernel deref of an attacker-chosen pointer. 3/3 stable baseline runs, guest surviving each (pool balanced). FIX VALIDATED: fix.diff (replenish-before-link; reload current mbuf on failure, mirroring the EOF branch) applied to the guest /usr/src, kernel rebuilt (nativekernel X86_64_GENERIC) and booted, same rig re-run against the patched kernels own handler: head stays NULL, no cycle, every mbuf freed exactly once, freed mbufs never delivered, NO ALIAS FOUND in both scenarios. Guest reset to clean-source snapshot afterwards.", "exploit_chain": "remote >2KB frames (VGE_RXCTL_RX_GIANT always on) + cluster-pool pressure -> vge_newbuf ENOBUFS ignored in the SOF branch -> mbuf double-referenced (head chain + RX ring) -> double free (vge_stop / EOF-branch vs next walk) -> same mbuf handed to two kernel owners (aliasing demonstrated) -> write through one owners mbuf header steers the other owners m_freem into an arbitrary kernel pointer (trap demonstrated) - on real VT612x hardware the entry needs no privilege; uid0 escalation was not pursued to completion because the target NIC class does not exist on the audit guest (rig dispatch is the established evidence class for hardware-gated drivers).", "evidence": "findings/poc/DF-4106/run.log + run.2.log/run.3.log (all markers: DOUBLE-REFERENCE, CYCLE DETECTED, vge_stop double frees, DANGLING ==m0/==m1 YES, if_input chain of freed mbufs [len=2044/2040], [A]/[B] ALIAS CONFIRMED); panic.txt (Fatal trap 9 at m_free+0x10 after the steered m_next := 0x4141414141414141); fix_run.log + fix_dmesg.txt ([A]/[B] NO ALIAS FOUND on the patched kernel); fix.diff (git-apply-able, validated); build.log/fix_build.log full untrimmed builds.", "kernel_refs": [ "sys/dev/netif/vge/if_vge.c:1294", "sys/dev/netif/vge/if_vge.c:1285", "sys/dev/netif/vge/if_vge.c:1328", "sys/dev/netif/vge/if_vge.c:1334", "sys/dev/netif/vge/if_vge.c:1161", "sys/dev/netif/vge/if_vge.c:2102", "sys/dev/netif/vge/if_vge.c:2118", "sys/dev/netif/vge/if_vge.c:1395", "sys/dev/netif/vge/if_vge.c:1837", "sys/kern/uipc_mbuf.c:1377" ], "poc_changes": "Seed pack claimed untested (hardware-gated) with no runnable PoC. Replaced with a real-handler dispatch rig: the guest kernel builds the vge driver in (device vge in X86_64_GENERIC), so the rig resolves the LIVE static vge_rxeof/vge_newbuf/vge_dma_map_addr via nm and injects the addresses through hex-string sysctls (plain int/long sysctls clamp >INT64_MAX). vge_rx_list_init/vge_dma_free are inlined in the kernel build and are replayed verbatim around the real vge_newbuf. Failure injection is the genuine production condition (pool drained until m_getcl(M_NOWAIT) fails). The seeds fix.diff was malformed (patch mangled it); re-authored mechanically and validated on a rebuilt kernel.", "attempts": 5, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 x86_64 (verify) / #1: Wed Oct 14 03:44:40 UTC 2026 x86_64 (fix-validation kernel)", "runtime_sec": 2700, "guest_dirty": 0, "build_cmd": "cd /root/df4106 && make -m /usr/src/share/mk", "run_cmd": "kldload ./df4106_vge_poc.ko && sh run.sh baseline (panic variant: sh run.sh panic; fixed: sh run.sh fixed after installing the patched kernel)", "code_hash": "7f15dade6a29f8576500ee860af049d03d69527086cbad881d85a19a2b18aba8", "notes": "Honest caveats recorded in VERDICT.md: (a) the seeds consequence-1 claim (chunk window re-arms a slot whose vge_rx_mbuf entry is a FREED mbuf) is conditional - a window covering the poisoned slot cannot complete while that slots own newbuf keeps failing (no consumed increment); the demonstrated paths do not depend on it; (b) the rigs static marker labels (DOUBLE-REFERENCE/DANGLING) keep their stock wording on the fixed kernel - the discriminators are the empty head chain, the absence of any prior free, and NO ALIAS FOUND; (c) panic variants 0x4141414141414141 write stands in for attacker influence over the aliased twin object (one owners header controls the others m_freem chase). Incidental (not this finding): /boot/kernel/if_vge.ko is built IFPOLL_ENABLE while the running kernels struct ifnet has no if_npoll member - a latent module/kern ABI mismatch. Guest left UP and clean (reset with-src; stock kernel #0; stock if_vge.c md5 f090bba601fd9f5d2d64e592fc378f02).", "recommended_fix": "In the SOF branch of vge_rxeof, replenish the descriptor BEFORE linking the mbuf into the head chain and on failure reload the current mbuf into the descriptor (mirror the EOF branch at if_vge.c:1328-1337) - see validated fix.diff.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src; kernel rebuilt via make nativekernel KERNCONF=X86_64_GENERIC (full log fix_build.log) and booted (#1 Wed Oct 14 03:44:40); identical rig re-dispatched against the patched kernels own vge_rxeof: no double-reference (head NULL), no cycle, no dangling frees, freed mbufs never delivered to if_input, NO ALIAS FOUND in both scenarios; guest then reset to stock.", "fix_evidence": "findings/poc/DF-4106/fix.diff; findings/poc/DF-4106/fix_run.log; findings/poc/DF-4106/fix_dmesg.txt; findings/poc/DF-4106/fix_build.log" } |