DragonFlyBSD Kernel Audit
DF-2661 / trigger_df2661.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-2661 trigger (v3): concurrent cold inode loads in the same 64KB DIO
# window, with the underlying read failed with EIO (media-fault simulation).
#
# Bug: _hammer2_io_getblk() completes the INPROG section with error != 0,
# leaving dio->bp set and DIO_GOOD clear.  Any other thread holding a ref on
# the DIO (taken while the first thread was inside the INPROG section) wakes
# up, acquires INPROG, and trips KKASSERT(dio->bp == NULL) at
# hammer2_io.c:254 -> panic.
#
# Media prep (once):
#   golden.img = dd if=/dev/vn2 of=... taken while the fs is still attached
#   (reads serve from the dirty device buffer cache).  NEVER use
#   `vnconfig -T`: it is O_TRUNC and zeroed the backing file.
# Per round: fresh clone of golden.img, attach WITHOUT -T, mount (cold
#   chains + cold device buffers => real strategy I/O in the INPROG
#   section), warm the path walk, arm the injector, blast 8 concurrent
#   cat loops at 8 distinct cold inodes sharing one 64KB window.

set -x
GOLD=/root/golden.img
WORK=/root/work.img
MNT=/mnt/h2661
ROUNDS=${1:-5}
ARM=${ARM:-1}      # ARM=0 -> do not arm the injector (control runs)
UMNT=${UMNT:-1}    # UMNT=0 -> skip the per-round umount

mkgolden() {
	umount $MNT 2>/dev/null
	vnconfig -u vn2 2>/dev/null
	umount $MNT 2>/dev/null
	vnconfig -u vn2 2>/dev/null
	rm -f /root/seed.img $GOLD
	truncate -s 512M /root/seed.img
	# -T is safe HERE because we want a fresh zero file anyway
	vnconfig -c -S 512m -T vn2 /root/seed.img || return 1
	newfs_hammer2 -L DATA /dev/vn2 || return 1
	mkdir -p $MNT
	mount_hammer2 /dev/vn2@DATA $MNT || return 1
	mkdir $MNT/dir || return 1
	i=1
	while [ $i -le 40 ]; do
		touch $MNT/dir/f$i || return 1
		i=$((i+1))
	done
	ls $MNT/dir | wc -l
	sync
	sleep 3
	# materialize the live device content (dirty buffers included)
	dd if=/dev/vn2 of=$GOLD bs=1m count=512 || return 1
	umount $MNT || return 1
	vnconfig -u vn2 || return 1
	rm -f /root/seed.img
	echo GOLDEN_OK
}

round() {
	vnconfig -u vn2 2>/dev/null
	rm -f $WORK
	cp $GOLD $WORK || return 2
	vnconfig -c -S 512m vn2 $WORK || return 2
	mount_hammer2 /dev/vn2@DATA $MNT || return 2
	# Warm path-walk chains (root inode, dir inode, dirent chains).
	# Plain ls does NOT stat the file inodes -> they stay cold.
	ls $MNT/dir >/dev/null 2>&1
	# Arm injector: fail every inode read on the 512MB test volume.
	[ "$ARM" = "1" ] && sysctl vfs.hammer2.df2661_fail_inode_read=1000000
	# 8 concurrent readers of 8 distinct cold inodes (same 64KB window).
	PIDS=""
	i=1
	while [ $i -le 8 ]; do
		( while :; do cat $MNT/dir/f$i >/dev/null 2>&1; done ) &
		PIDS="$PIDS $!"
		i=$((i+1))
	done
	sleep 15
	for p in $PIDS; do kill $p 2>/dev/null; done
	wait 2>/dev/null
	sysctl vfs.hammer2.df2661_fail_inode_read=0
	[ "$UMNT" = "1" ] && umount $MNT 2>/dev/null
	echo ROUND_SURVIVED
}

case "$1" in
golden) mkgolden ;;
*)
	n=1
	while [ $n -le $ROUNDS ]; do
		echo "=== ROUND $n ==="
		round || break
		n=$((n+1))
	done
	echo "ALL_ROUNDS_SURVIVED"
	;;
esac