#!/bin/sh
# DF-5773 run driver v3 (on the guest, as root).
# usage: run5773.sh [delay_secs=0.10]
#
# The monitor installs handle_term AFTER fork() (init.c:206-218), so a
# FIRST-boot child has SIGTERM=SIG_DFL.  But after a reboot cycle
# (cpu_reset() exit(EX_VKERNEL_REBOOT) -> monitor re-loops -> fork #2),
# child #2 INHERITS handle_term, and in the child childpid==0, so
# handle_term's kill(childpid, sig) (init.c:505) becomes kill(0, SIGTERM):
# a signal to the ENTIRE PROCESS GROUP.  Delivered while SIGTERM is
# self-masked inside its own handler, it re-arms itself forever
# (handler -> kill(0) -> pending TERM on self -> sigreturn -> redelivery).
#
# Sequence: boot vkroot to single-user, run `reboot` inside the guest
# (clean cpu_reset -> monitor refork), then fire one SIGTERM during
# child #2's early-boot window (fork -> SI_BOOT2_PROC0 initshutdown).
#
# EXPECTED (vulnerable): log frozen before "Mounting root" of the SECOND
# boot, NO "Caught SIGTERM", kernel.debug alive at ~100% CPU, second TERM
# useless, and the group splash: the runner's `sleep` sibling is TERMed
# dead (kill(0) hit the whole pgrp) and the runner itself receives TERM
# (trapped+logged).
DELAY=${1:-0.10}
VK=/usr/obj/usr/src/sys/VKERNEL64/kernel.debug
cd /usr/obj/usr/src/sys/VKERNEL64

pkill -9 -f kernel.debug 2>/dev/null; sleep 1
rm -f /tmp/vkin /tmp/vk5773.log /tmp/vk5773.obs
mkfifo /tmp/vkin
(sleep 900 > /tmp/vkin) &
HOLDER=$!

sleep 300 &
SLEEPER=$!

trap 'echo "RUNNER_GOT_SIGTERM $(date +%s)" >> /tmp/vk5773.obs' TERM

./kernel.debug -m 512m -r /vkroot.img -n 2 -e init_path=/bin/vkinit -s \
    < /tmp/vkin > /tmp/vk5773.log 2>&1 &
VKPID=$!
echo "RUN monitor pid=$VKPID sleeper=$SLEEPER delay=$DELAY" > /tmp/vk5773.obs

# now that the vkernel holds the read end, open ours and answer the
# mountroot prompt immediately (buffered input, DF-5661 rig trick)
exec 3>/tmp/vkin
echo "ufs:vkd0" >&3

# phase 1a: first boot reaches the vkinit single-user shell
i=0
while [ $i -lt 60 ]; do
	grep -q "can't access tty" /tmp/vk5773.log 2>/dev/null && break
	kill -0 $VKPID 2>/dev/null || break
	sleep 1
	i=$((i+1))
done
echo "PHASE1 shell up i=$i" >> /tmp/vk5773.obs
sleep 2

# phase 1b: reboot(2) from process context inside the guest ->
# kern_reboot -> cpu_reset() exit(32) -> monitor refork ->
# child #2 INHERITS handle_term (sigactions ran in the monitor loop after
# fork #1; init.c:206-218 + 505)
echo "/bin/reboot2" >&3
i=0
while [ $i -lt 1200 ]; do
	n=$(grep -ac "Using anonymous memory" /tmp/vk5773.log 2>/dev/null)
	[ "${n:-0}" -ge 2 ] && break
	kill -0 $VKPID 2>/dev/null || { echo "VK_DIED_BEFORE_REFORK" >> /tmp/vk5773.obs; break; }
	sleep 0.05
	i=$((i+1))
done
echo "PHASE1 refork n=$n" >> /tmp/vk5773.obs

# phase 2: TERM burst into child #2's early-boot window
# (fork -> SI_BOOT2_PROC0 initshutdown replaces the handler)
k=0
while [ $k -lt 20 ]; do
	kill -TERM $VKPID 2>/dev/null
	sleep 0.05
	k=$((k+1))
done
echo "FIRING TERM burst (20 x 0.05s)" >> /tmp/vk5773.obs

# snapshot marker counts before the burst's effects settle
pre_caught=$(grep -ac "Caught SIGTERM" /tmp/vk5773.log 2>/dev/null)
pre_mount=$(grep -ac "Mounting root" /tmp/vk5773.log 2>/dev/null)
pre_anon=$(grep -ac "Using anonymous memory" /tmp/vk5773.log 2>/dev/null)

i=0
while [ $i -lt 20 ]; do
	sleep 1
	post_caught=$(grep -ac "Caught SIGTERM" /tmp/vk5773.log 2>/dev/null)
	kill -0 $VKPID 2>/dev/null || { echo "VK_DEAD" >> /tmp/vk5773.obs; break; }
	i=$((i+1))
done

sleep 1
post_caught=$(grep -ac "Caught SIGTERM" /tmp/vk5773.log 2>/dev/null)
post_mount=$(grep -ac "Mounting root" /tmp/vk5773.log 2>/dev/null)
post_anon=$(grep -ac "Using anonymous memory" /tmp/vk5773.log 2>/dev/null)
echo "COUNTS anon pre=$pre_anon post=$post_anon mount pre=$pre_mount post=$post_mount caught pre=$pre_caught post=$post_caught" >> /tmp/vk5773.obs
if kill -0 $SLEEPER 2>/dev/null; then
	echo "SLEEPER_ALIVE (no group splash)" >> /tmp/vk5773.obs
else
	echo "SLEEPER_DEAD_BY_GROUP_SPLASH" >> /tmp/vk5773.obs
fi

kill -TERM $VKPID 2>/dev/null && echo "SECOND_TERM_SENT" >> /tmp/vk5773.obs
sleep 2
kill -0 $VKPID 2>/dev/null && echo "STILL_ALIVE_AFTER_SECOND_TERM(wedged)" >> /tmp/vk5773.obs || echo "DIED" >> /tmp/vk5773.obs

echo "=== CPU state ===" >> /tmp/vk5773.obs
ps aux | grep -a "[k]ernel.debug" >> /tmp/vk5773.obs 2>/dev/null
echo "(end)" >> /tmp/vk5773.obs

kill $HOLDER 2>/dev/null
kill -9 $SLEEPER 2>/dev/null
pkill -9 -f kernel.debug 2>/dev/null
exec 3>&-
cp /tmp/vk5773.log /root/vk5773.last.log 2>/dev/null
exit 0
