/*
 * sysarch_surface.c — unprivileged probe of the pc64 sysarch(2) surface
 * and the IOPL state machine's user-reachable fences.
 *
 * Run as: unprivileged user (maxx).
 *
 * Checks:
 *  1. syscall 165 (sysarch) with assorted cmd values — on pc64
 *     sys/platform/pc64/x86_64/sysarch.c:46-49 is a hard EOPNOTSUPP stub:
 *     no dispatch, no copyin, no IOPERM/LDT handling at all.
 *  2. open("/dev/io") must fail EPERM for unprivileged users
 *     (kern/kern_memio.c:184-195 gates with SYSCAP_RESTRICTEDROOT).
 *  3. Forge attempt: setcontext() with uc_mcontext.mc_rflags |= PSL_IOPL
 *     (routes through SYS_sigreturn). machdep.c:1023 EFL_SECURE must
 *     reject it with EINVAL (PSL_USERCHANGE excludes PSL_IOPL, psl.h:81).
 *  4. Report actual IOPL via pushfq at each step.
 */
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/ucontext.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef SYS_sysarch
#define SYS_sysarch 165
#endif

static unsigned iopl_cur(void)
{
	unsigned long rf;
	__asm __volatile("pushfq; popq %0" : "=r"(rf));
	return (unsigned)((rf >> 12) & 3);
}

int main(void)
{
	int i, r, fd;
	long cmds[] = { 0, 1, 2, 3, 101 /* IOPERM-ish */, 102, 103,
			0x7f /* LDT-ish */, 0x1234 };

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("== sysarch surface probe (uid=%d) ==\n", getuid());
	for (i = 0; i < (int)(sizeof(cmds) / sizeof(cmds[0])); i++) {
		errno = 0;
		r = syscall(SYS_sysarch, cmds[i], (void *)0x4141414141414141UL);
		printf("sysarch(cmd=%5ld, garbage ptr) = %d errno=%d (%s)\n",
		       cmds[i], r, errno, strerror(errno));
	}

	printf("\n== /dev/io open as unprivileged ==\n");
	fd = open("/dev/io", O_RDWR);
	if (fd < 0)
		printf("open(/dev/io, O_RDWR) = -1 errno=%d (%s)  [expected EPERM/ACCE]\n",
		       errno, strerror(errno));
	else {
		printf("open(/dev/io, O_RDWR) = %d  *** UNPRIV OPEN SUCCEEDED ***\n", fd);
		close(fd);
	}
	fd = open("/dev/io", O_RDONLY);
	if (fd < 0)
		printf("open(/dev/io, O_RDONLY)= -1 errno=%d (%s)  [expected EPERM/ACCE]\n",
		       errno, strerror(errno));
	else {
		printf("open(/dev/io, O_RDONLY)= %d  *** UNPRIV OPEN SUCCEEDED ***\n", fd);
		close(fd);
	}

	printf("\n== sigreturn rflags forge attempt (expect EINVAL, IOPL stays 0) ==\n");
	{
		ucontext_t uc;
		getcontext(&uc);
		uc.uc_mcontext.mc_rflags |= 0x3000UL; /* PSL_IOPL */
		errno = 0;
		r = setcontext(&uc);	/* returns only on failure */
		printf("setcontext(forged mc_rflags|0x3000) returned %d errno=%d (%s)\n",
		       r, errno, strerror(errno));
		printf("IOPL now = %u (want 0)\n", iopl_cur());
	}

	printf("\n== final IOPL = %u ==\n", iopl_cur());
	return 0;
}
