⬢ DragonFlyBSD Kernel Audit
DF-3161 / umount_fifoleak.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-3161 PoC: `umount -f` force-close loop never passes FREAD, so
# fifo_close() never decrements fi_readers -> the fifoinfo and its
# connected AF_LOCAL socket pair are leaked permanently.
#
# Observable: connected unix-domain socket count (netstat -f unix).
#   CONTROL: fifo opened on tmpfs, holder exits normally -> count returns
#            to baseline (fifo_close destroys the fip).
#   BUG RUN: fifo opened on tmpfs, `umount -f` while held, then holder
#            exits -> count stays +2 above baseline forever (sockets leaked).
#
# Must run as root (needs mount/umount -f).
set -u

D=/mnt/df3161
count_sockpairs() {
	netstat -f unix 2>/dev/null | awk '$2 == "stream" { c++ } END { print c + 0 }'
}

[ "$(id -u)" = 0 ] || { echo "must be root"; exit 2; }
mkdir -p "$D"

echo "== baseline =="
BASE=$(count_sockpairs)
echo "baseline connected-stream sockets: $BASE"

echo
echo "== CONTROL: open fifo on tmpfs, close normally (no umount -f) =="
mount -t tmpfs tmpfs "$D" || exit 2
mkfifo "$D/f"
./fifo_hold "$D/f" &
H=$!
sleep 1
CTRL_OPEN=$(count_sockpairs)
echo "with fifo open: $CTRL_OPEN (baseline+$((CTRL_OPEN-BASE)); +2 expected)"
kill -TERM $H; wait $H 2>/dev/null
sleep 1
CTRL_CLOSED=$(count_sockpairs)
echo "after holder exit: $CTRL_CLOSED (baseline+$((CTRL_CLOSED-BASE)); 0 expected if no leak)"
umount "$D" || echo "(control umount failed: $?)"

echo
echo "== BUG RUN: open fifo on tmpfs, umount -f, then close =="
mount -t tmpfs tmpfs "$D" || exit 2
mkfifo "$D/f"
./fifo_hold "$D/f" &
H=$!
sleep 1
BUG_OPEN=$(count_sockpairs)
echo "with fifo open: $BUG_OPEN (baseline+$((BUG_OPEN-BASE)); +2 expected)"
echo "umount -f $D"
umount -f "$D" || echo "(umount -f rc=$?)"
kill -TERM $H; wait $H 2>/dev/null
sleep 1
BUG_CLOSED=$(count_sockpairs)
echo "after holder exit: $BUG_CLOSED (baseline+$((BUG_CLOSED-BASE)))"
sleep 3
BUG_CLOSED2=$(count_sockpairs)
echo "after 3s more:    $BUG_CLOSED2 (baseline+$((BUG_CLOSED2-BASE)))"
umount "$D" 2>/dev/null

echo
echo "== VERDICT =="
if [ $((BUG_CLOSED - BASE)) -ge 2 ] && [ $((CTRL_CLOSED - BASE)) -eq 0 ]; then
	echo "reproduced: socket pair leaked after umount -f + close " \
	     "(+2 sockets persist; control returns to baseline)"
	exit 0
else
	echo "not_reproduced (ctrl delta=$((CTRL_CLOSED-BASE)) bug delta=$((BUG_CLOSED-BASE)))"
	exit 1
fi