DF-3501 / verdict.json
{ "finding_id": "DF-3501", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "ng_fec_addport blindly casts any ifnet to struct arpcom with no if_type check: for non-arpcom members it writes an 8-byte ng-node pointer at member_ifp+sizeof(struct ifnet)+16 and 6 known MAC bytes at +0, and (first-member path) reads 6 bytes from the same location, disclosed as the fec interface MAC. Proven on the guest three ways: (1) dumpif delta on lo0 (standalone kmalloc(928) ifnet): vtnet0s MAC and the fec node pointer appear at +928/+944 past the allocation (absorbed by 32B slab slack on this build \u2014 write proven, cross-object impact latent); (2) unprivileged leak: ifconfig fec1 shows the low 6 bytes of gif1->gif_psrc (0xfffff8004f0a2df0 kmem pointer); (3) deterministic panic: adding gif0 overwrites gif_softc.gif_psrc with garbage -> ifconfig gif0 -> GPF in gif_ioctl. Fixed and validated: if_type != IFT_ETHER rejected (EPROTONOSUPPORT), lo0 tail unchanged, gifs functional.", "exploit_chain": "create fec node -> add_iface <ether-ifp> (first member, sets bundle MAC) -> add_iface lo0|gifN (arpcom cast writes node ptr + known MAC past the members ifnet allocation / into gif_softc fields) -> leak readback via ifconfig (unprivileged) or victim deref (ifconfig gifN -> kernel panic)", "evidence": "run.log (full dumpif before/after + leak + panic sequence); leak_sample.txt (f0:2d:0a:4f:00:f8 = low bytes of 0xfffff8004f0a2df0); panic.txt (Fatal trap 9 at gif_ioctl+0x9f); fecdrive.c/dumpif.c sources; VERDICT.md fix-validation section", "kernel_refs": [ "sys/netgraph/fec/ng_fec.c:378", "sys/netgraph/fec/ng_fec.c:379", "sys/netgraph/fec/ng_fec.c:388", "sys/netgraph/fec/ng_fec.c:404", "sys/net/if_loop.c:111", "sys/net/gif/if_gif.h:51", "sys/net/if_arp.h:117" ], "poc_changes": "none to the defect code; lab enablers E1/E2/E3 (documented in setup_guest.sh / ../DF-3500) required to reach addport on the INVARIANTS guest; fecdrive.ko drives the node from thread context", "attempts": 2, "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": 2400, "guest_dirty": 0, "build_cmd": "sh setup_guest.sh # enablers + make in /usr/src/sys/netgraph/fec + fecdrive/dumpif kmods", "run_cmd": "sysctl -n debug.fecdrive=create; sysctl -n debug.fecdrive=add vtnet0; sysctl -n debug.fecdrive=add lo0; sysctl -n debug.fecdrive=addg gif1; sysctl -n debug.fecdrive=addg gif0; ifconfig gif0", "code_hash": "e8cf25ff47210caf1634a98849d42b0c3a21526ed2c514447dbbe92969d59bae", "notes": "Control-plane trigger is root-only on stock DFly (ng_socket caps_priv_check); leak readback needs no privilege. lo0 write lands in slab slack on this build (latent); gif corruption is in-allocation type confusion and panics deterministically. No privesc chain claimed (fixed value/offset, privileged trigger).", "recommended_fix": "Reject non-ethernet members: if (bifp->if_type != IFT_ETHER) return EPROTONOSUPPORT in ng_fec_addport", "fix_status": "fixed", "fix_notes": "fix.diff validated in-guest: lo0/gif1/gif0 rejected with EPROTONOSUPPORT, lo0 tail all zeros (no OOB write), gifs functional, vtnet0 membership unaffected" } |