#!/bin/sh
# DF-0632 — ipfw3 per-CPU state counters are monotonic (incremented on
# state creation but never decremented on expiry/flush).
#
# Demonstrates that after `state_max_icmp_out` lifetime creations, the
# firewall permanently stops creating new states for that proto/direction.
#
# REQUIRES:
#   - ipfw3.ko + ipfw3_basic.ko kernel modules (loadable; root)
#   - The DF0632-probe.ko instrumentation module built from
#     ipfw3_counter_probe.c (to read kernel counters via kldload)
#   - Run as root (firewall administration is privileged).
#
# RUN: sh df0632_test.sh
# EXPECTED on vulnerable kernel:
#   - icmp_out counter goes 0 -> 6 after round 1 (5 is max+1)
#   - icmp_out counter STAYS at 6 after timeout/expiry
#   - Subsequent pings CANNOT create state; counter pinned forever
set +e
OUT=/tmp/df0632_result.txt
PROBE_KO=${PROBE_KO:-/usr/src/sys/modules/df0632_probe/df0632_probe.ko}

# kldload ipfw3 disables network by default-deny; we run as nohup so SSH
# drops but the script keeps running and writes results to $OUT.
{
echo "=== DF-0632 ipfw3 state counter monotonic-increment test ==="
date

# Modules must already be loaded
kldstat | head

sysctl -w net.inet.ip.fw3.enable=1
/sbin/ipfw3 -f flush
/sbin/ipfw3 add 100 allow tcp from any to any       # keep SSH alive
/sbin/ipfw3 add 200 allow icmp from any to any keep-state
/sbin/ipfw3 list

# Configure small max for fast exhaustion
sysctl -w net.inet.ip.fw3_basic.state_max_icmp_out=5
sysctl -w net.inet.ip.fw3_basic.icmp_timeout=3
sysctl -w net.inet.ip.fw3_basic.cleanup_interval=1

echo "--- Initial counter (should be 0) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

echo "--- Round 1: send 6 ICMP packets with unique dst IPs ---"
for i in 1 2 3 4 5 6; do
    ping -c 1 -t 1 -W 5 10.0.2.$((i+100)) >/dev/null 2>&1
done
sleep 1
echo "--- Counter after round 1 (expect icmp_out=6: 5 max + 1 over) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

echo "--- Wait for state expiry (timeout=3, cleanup=1) ---"
sleep 6
echo "--- Counter AFTER expiry (BUG: counter still 6, NOT decremented) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

echo "--- Round 2: send 6 MORE pings ---"
for i in 11 12 13 14 15 16; do
    ping -c 1 -t 1 -W 5 10.0.2.$((i+100)) >/dev/null 2>&1
done
sleep 1
echo "--- Counter after round 2 (BUG: should still be 6; counter > max so no new states) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

echo "--- Wait for second expiry ---"
sleep 6
echo "--- Counter after second expiry (BUG: cumulative, never decremented) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

echo "--- Round 3: send 5 more pings (counter is pinned) ---"
for i in 21 22 23 24 25; do
    ping -c 1 -t 1 -W 5 10.0.2.$((i+100)) >/dev/null 2>&1
done
sleep 1
echo "--- Final counter (BUG: counter still pinned; no new states can ever be created) ---"
kldload "$PROBE_KO"; dmesg | tail -8 | grep "DF0632 cpu0"; kldunload df0632_probe

/sbin/ipfw3 show
sysctl -w net.inet.ip.fw3.enable=0

date
echo "=== Done ==="
} > "$OUT" 2>&1
echo "Result written to $OUT"
