DF-3362 / verdict.json
{ "finding_id": "DF-3362", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced deterministically: with `scrub in on tap0 all fragment crop`, two tap-injected IPv4 fragments (first fills cache range [0,8000), second overlaps at off=496 with 8000B payload, arriving as the ~35-mbuf MGET chain tapwrite() always builds) hit pf_fragcaches precut>0 chop path: m_dup() of the multi-mbuf chain, tail-trim via m_adj(dup, negative) which zeroes but does NOT unlink trailing mbufs, then KASSERT((*m0)->m_next == NULL) at pf_norm.c:667 fires -> panic: (*m0)->m_next != NULL: pf_fragcache with backtrace pf_fragcache -> pf_normalize_ip -> pf_test -> pf_check_in -> pfil_run_hooks. Guest died in DDB; hard reset required. The twin KASSERT at :684 rests on the same single-mbuf assumption. INVARIANTS/debug kernels only: production non-INVARIANTS kernels compile the KASSERTs out and continue with zero-length mbufs chained (benign for the packet, violates the invariants). Fix validated in-guest: single-fix kernel (#1, 2026-09-11) with fix.diff (NULL-check reorder + unlink zero-length tail + assert on m_pkthdr.len) \u2014 same trigger no longer panics, regression controls (max-mss path, reassemble path) pass. The reorder also closes known DF-0391s m_dup NULL deref in the same hunk.", "exploit_chain": "", "evidence": [ "findings/poc/DF-3362/panic.txt (serial console: panic string + backtrace)", "findings/poc/DF-3362/fragcache_kassert.c (two-frame tap injector)", "findings/poc/DF-3362/fix_validation.md (baseline panic vs patched no-panic, same trigger)", "sys/kern/uipc_mbuf.c:1859 (m_adj tail-trim zeroes trailing mbufs without unlinking)", "sys/net/tap/if_tap.c tapwrite (MGETHDR+MGET chain input path)" ], "kernel_refs": [ "sys/net/pf/pf_norm.c:661-669", "sys/net/pf/pf_norm.c:670-687", "sys/kern/uipc_mbuf.c:1859-1917", "sys/net/tap/if_tap.c:927-985" ], "poc_changes": "Seed rewritten: lo0 raw-socket injection cannot carry >16KB multi-mbuf fragments (lo0 MTU cap 16384, TAPMRU-style limits, local-source validation); switched to /dev/tap0 Ethernet-frame injection which reproduces the multi-mbuf input shape exactly (tapwrite builds MGETHDR+MGET chains).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0/#1: stock X86_64_GENERIC INVARIANTS kernel for the panic; #1 Fri Sep 11 10:20:27 UTC 2026 single-fix kernel for validation", "runtime_sec": 900, "guest_dirty": 0, "build_cmd": "cc -O2 -o fragcache_kassert fragcache_kassert.c # fix build: cd /usr/src && patch -p1 < fix.diff && make nativekernel && make installkernel", "run_cmd": "pfctl -f fragcrop.conf && pfctl -e && /root/poc/fragcache_kassert", "code_hash": "01b01b542be7073404bee9a5281b907eef64498827324bc1af60275d872f9081", "notes": "Severity Low: remote-deterministic two-packet panic but gated on INVARIANTS kernels + fragcrop scrub mode. Non-INVARIANTS kernels proceed unharmed. Guest was hard-reset (vm.sh reset with-src) after the baseline panic and will be reset again after this run.", "recommended_fix": "See fix.diff: reorder m_dup NULL check before m_adj, unlink zero-length trailing mbufs after the tail-trim, and assert on m_pkthdr.len instead of m->m_len. Validated in-guest.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Fri Sep 11 10:20:27 UTC 2026 x86_64 (X86_64_GENERIC + fix.diff)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline kernel #0 panicked on the trigger (panic.txt). Single-fix kernel #1 with fix.diff: identical trigger completes with EXIT=0, no panic, guest healthy; scrub max-mss and reassemble regression controls pass. The panic is gone and the chop path works.", "fix_evidence": [ "findings/poc/DF-3362/fix_validation.md", "findings/poc/DF-3362/fixbuild_excerpt.txt", "findings/poc/DF-3362/panic.txt" ] } |