DF-3381 / verdict.json
{ "finding_id": "DF-3381", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "catchpacket() computes bh_caplen = totlen - hdrlen after clamping totlen to bd_bufsize; on x86-64 bif_hdrlen is 34 (ether) / 36 (DLT_NULL) > BPF_MINBUFSIZE=32, so BIOCSBLEN(32)+BIOCSETIF makes the subtraction underflow to 0xfffffffc and the tap copy routine writes ~4GiB of packet data through a kmalloc(32) store buffer. Reproduced deterministically on the stock INVARIANTS guest: panic: bpf_mcopy with backtrace catchpacket<-bpf_mtap<-bpf_ptap<-if_simloop; the corrupting ioctls and packet were issued by a uid=nobody child holding the bpf fd opened by its root parent (tcpdump-style privilege drop). Primitive: forward linear heap overflow from kmalloc(32)+hdrlen with fully attacker-controlled contents but uncontrolled (~4GiB, forced) length - corrupt-then-panic, no credible survive-and-exploit path, so impact ceiling is reliable kernel memory corruption/DoS gated on holding an open bpf descriptor (device is 0600 root; jails refused). Fix validated on rebuilt kernel: PoC now fails cleanly with EINVAL at BIOCSETIF and the guest survives.", "exploit_chain": "root parent opens /dev/bpf0 + BIOCSBLEN(32) -> fork/setuid(nobody) child -> BIOCSETIF(lo0) -> one loopback UDP packet -> catchpacket bh_caplen underflow -> bpf_mcopy writes packet-controlled bytes at kmalloc(32)+36 with length 0xfffffffc -> heap corruption of adjacent slab objects -> panic (bpf_mcopy chain-end KASSERT). uid0 not pursued: copy length is forced, kernel always dies; trigger requires a bpf fd (privileged open).", "evidence": [ "panic.txt: panic: bpf_mcopy + catchpacket/bpf_mtap/bpf_ptap/if_simloop backtrace", "run.log: [+] BIOCSBLEN -> 32 then guest dies in the setuid(nobody) child phase", "trigger.c: root opens fd, drops to nobody, binds lo0, fires one UDP packet", "VERDICT.md: primitive characterization + why uid0 is blocked by the forced 4GiB copy length" ], "kernel_refs": [ "sys/net/bpf.c:794-806", "sys/net/bpf.c:1128-1134", "sys/net/bpf.c:1418-1421", "sys/net/bpf.c:1462", "sys/net/bpf.c:1543", "sys/net/bpf.h:61-62", "sys/net/bpf.h:64-67", "sys/net/bpf.h:134-149" ], "poc_changes": "Seed sketch did not exist; PoC written fresh. Key correct details: BIOCSBLEN before BIOCSETIF (only then are buffers allocated); lo0 (DLT_NULL, bif_hdrlen=36) as victim interface; loopback UDP sendto as traffic source (if_simloop->bpf_ptap->bpf_mtap); fork+setuid(nobody) so the corrupting ioctls run unprivileged at trigger time.", "attempts": 1, "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": 35, "guest_dirty": 1, "build_cmd": "cc -O -o trigger trigger.c", "run_cmd": "./trigger", "code_hash": "020c418092350543bc084e68c7ffb2a509c66f21ce19e4c80064e1d49fb69b71", "notes": "Alternate trigger: debug.bpf_bufsize sysctl (RW, unclamped at open, bpf.c:98-100,380) below bif_hdrlen. Ethernet variant: bif_hdrlen=34. Impact panic + demonstrated controlled heap corruption; PR:H on default perms (0600 cdev, jail-refusing open) with the dropped-privilege fd-holder caveat documented in VERDICT.md.", "recommended_fix": "Reject BIOCSETBLEN once buffers are allocated, reject BIOCSETIF when bd_bufsize < bif_hdrlen, and drop (count) in catchpacket when totlen < hdrlen (validated fix.diff).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Sep 11 22:24:51 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (fix.diff applied; make nativekernel + installkernel in guest)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline (stock #0 kernel): deterministic panic: bpf_mcopy via catchpacket underflow. Patched (#1 kernel, fix.diff): identical PoC fails cleanly with BIOCSETIF(lo0): EINVAL from the bpf_setif bd_bufsize<bif_hdrlen guard; guest survives; normal capture verified working (bh_hdrlen=36 bh_caplen=38 bs_recv=2).", "fix_evidence": [ "run.patched.log", "kernel_build.log (make nativekernel/installkernel RC=0)" ] } |