#!/bin/sh
# DF-0606 — DDB-context locking defect in _db_show_mesh
#
# This is a DDB-only privileged-context bug. The vulnerable call chain is:
#
#   _db_show_mesh()                       [sys/netproto/802_11/wlan/ieee80211_ddb.c:908]
#     -> ieee80211_mesh_rt_update(rt, 0)   [sys/netproto/802_11/wlan/ieee80211_mesh.c:275]
#          -> MESH_RT_ENTRY_LOCK(rt)       == lockmgr(&(rt)->rt_lock, LK_EXCLUSIVE)
#                                          [sys/netproto/802_11/ieee80211_dragonfly.h:607]
#
# Acquiring a sleepable lockmgr lock from inside DDB (panic/frozen-scheduler
# context) is forbidden; the helper also MUTATES rt_updtime/rt_flags/rt_lifetime
# on every entry, so the display corrupts the table it is dumping.
#
# Live DDB reproduction requires a mesh vap (ieee80211vap with iv_opmode ==
# IEEE80211_M_MBSS and iv_mesh != NULL) with at least one entry in
# ms->ms_routes. That requires IEEE80211 (WiFi) hardware. This KVM audit
# guest has only vtnet0/lo0 — no wlan device can be created — so the live
# path cannot be exercised end-to-end here.
#
# What CAN be demonstrated on any DFly kernel with options DDB +
# IEEE80211_SUPPORT_MESH (both on by default in X86_64_GENERIC) is:
#
#   (1) the `show mesh` / `show vap` ddb commands are registered,
#   (2) the buggy call is statically present in /boot/kernel/kernel at the
#       exact source line cited (addr2line proof), and
#   (3) `ieee80211_mesh_rt_update` is linked into the kernel and itself
#       begins with the lockmgr acquisition.
#
# This script performs (1)-(3). After the fix is built and booted, re-running
# it confirms the offending call instruction is GONE from _db_show_mesh.
#
# usage:  verify.sh <baseline|patched>
#   baseline -> expects the call to ieee80211_mesh_rt_update to be present
#                in _db_show_mesh (BUG PRESENT)
#   patched  -> expects the call to be ABSENT from _db_show_mesh (FIX APPLIED)
#
# Exits 0 on the expected outcome, non-zero otherwise.

set -eu
MODE="${1:-baseline}"
KDBG=/boot/kernel/kernel.debug

ok() { echo "PASS: $*"; }
bad() { echo "FAIL: $*"; exit 1; }

echo "=== DF-0606 verify ($MODE) on $(sysctl -n kern.version | head -1) ==="

# (1) DDB compiled in?
if [ "$(sysctl -n debug.debugger_on_panic 2>/dev/null || echo 0)" = "1" ]; then
    ok "debug.debugger_on_panic=1 (DDB wired into the kernel)"
else
    echo "WARN: debug.debugger_on_panic not 1 (DDB may still be compiled in)"
fi

# (2) ieee80211_mesh_rt_update present in the kernel symbol table?
RT_UPD=$(nm /boot/kernel/kernel 2>/dev/null | awk '/ T ieee80211_mesh_rt_update$/{print $1; found=1} END{if(!found) print "NOTFOUND"}')
[ "$RT_UPD" = "NOTFOUND" ] && bad "ieee80211_mesh_rt_update not linked into /boot/kernel/kernel"
ok "ieee80211_mesh_rt_update @ 0x$RT_UPD"

# (3) Find every call site of ieee80211_mesh_rt_update and resolve each to source.
#     _db_show_mesh is static; addr2line -e kernel.debug resolves it.
echo
echo "--- call sites of ieee80211_mesh_rt_update in /boot/kernel/kernel.debug ---"
CALLS=$(objdump -d "$KDBG" 2>/dev/null | \
        grep -B0 "callq.*<ieee80211_mesh_rt_update>$" | \
        awk '{print $1}' | sed 's/://')
IN_DDB_MESH=0
IN_OTHER=0
for c in $CALLS; do
    # strip 8-digit leading zeros for addr2line
    loc=$(addr2line -f -e "$KDBG" "0x$c" 2>/dev/null)
    fn=$(echo "$loc" | head -1)
    where=$(echo "$loc" | tail -1)
    printf "  %-18s -> %-24s %s\n" "$c" "$fn" "$where"
    case "$fn" in
        _db_show_mesh) IN_DDB_MESH=$((IN_DDB_MESH+1));;
        *)              IN_OTHER=$((IN_OTHER+1));;
    esac
done

echo
echo "summary: $IN_DDB_MESH call(s) inside _db_show_mesh, $IN_OTHER legit call(s) elsewhere"

case "$MODE" in
    baseline)
        [ "$IN_DDB_MESH" -ge 1 ] && ok "baseline BUG PRESENT: _db_show_mesh calls ieee80211_mesh_rt_update (lockmgr from DDB)" \
                                 || bad "baseline: expected >=1 call in _db_show_mesh, found $IN_DDB_MESH"
        ;;
    patched)
        [ "$IN_DDB_MESH" -eq 0 ] && ok "patched BUG GONE: _db_show_mesh no longer calls ieee80211_mesh_rt_update" \
                                 || bad "patched: expected 0 calls in _db_show_mesh, still $IN_DDB_MESH"
        ;;
    *) bad "unknown mode '$MODE'; use baseline|patched";;
esac

# (4) Bonus: confirm rt_lifetime is the field we now read instead.
echo
echo "--- rt_lifetime field offset in struct ieee80211_mesh_route ---"
grep -n "rt_lifetime" /sys/netproto/802_11/ieee80211_mesh.h 2>/dev/null || true

ok "DONE ($MODE)"
