โฌข DragonFlyBSD Kernel Audit
DF-1843 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1843 source-confirmation harness (dm dmopen vs dm_dev_remove_ioctl UAF).
 *
 * sys/dev/disk/dm/device-mapper.c:209-213 dmopen:
 *   dmv = dm_dev_lookup(...);    // ref_cnt++
 *   dmv->is_open = 1;            // plain store, NO lock
 *   dm_dev_unbusy(dmv);          // ref_cnt-- (released immediately!)
 *
 * dm_dev_remove_ioctl (dm_ioctl.c:349-361) reads is_open with NO lock at
 * L354, races against dmopen's is_open=1 store.  If the remove reads
 * is_open==0 between dm_dev_lookup and is_open=1, it proceeds to
 * dm_dev_remove -> disable_dev (waits ref_cnt==0) -> dm_dev_destroy ->
 * dm_dev_free -> kfree(dmv).  dmopen then returns 0 with dev->si_drv1
 * pointing at freed dmv.  Next dmstrategy (md.c:365) dereferences freed
 * memory.
 *
 * This needs `device dm` (dm.ko NOT loaded on this guest -> /dev/mapper/
 * control absent) AND operator-group membership (maxx is not operator).
 * Both gates absent here, so this is a logic-only harness documenting the
 * refcount race.
 *
 * Build:  cc -O2 -o harness harness.c -lpthread
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

static volatile int ref_cnt;
static volatile int is_open;       /* plain store, no lock (mirrors dmopen) */
static volatile int freed;         /* set when "kfree(dmv)" has happened */
static volatile int stop;
static volatile int race_won;

static void *dm_open_thread(void *a) {
    (void)a;
    while (!stop && !race_won) {
        ref_cnt++;                                  /* dm_dev_lookup */
        /* WINDOW: remove thread can read is_open==0 here */
        is_open = 1;                                /* dmopen L212 โ€” plain store */
        ref_cnt--;                                  /* dm_dev_unbusy */
        if (freed) { race_won = 1; break; }         /* returned fd into freed dmv */
        is_open = 0;
        usleep(1);
    }
    return NULL;
}

static void *dm_remove_thread(void *a) {
    (void)a;
    while (!stop && !race_won) {
        if (is_open == 0) {                         /* dm_ioctl.c:354 โ€” read, no lock */
            ref_cnt++;                              /* lookup in dm_dev_remove */
            while (ref_cnt > 1 && !stop) ;          /* disable_dev waits ref==0 */
            freed = 1;                              /* dm_dev_free / kfree(dmv) */
            race_won = 1;
            break;
        }
        usleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t t_open, t_remove;
    pthread_create(&t_open,   NULL, dm_open_thread,   NULL);
    pthread_create(&t_remove, NULL, dm_remove_thread, NULL);

    /* Run briefly; the race window is wide in the kernel (multiple CPU
     * cycles between the lookup and the is_open store). */
    for (int i = 0; i < 100 && !race_won; i++) usleep(1000);
    stop = 1;
    pthread_join(t_open, NULL);
    pthread_join(t_remove, NULL);

    if (race_won) {
        printf("DF-1843: race confirmed โ€” dmopen returned a handle into a "
               "freed dm_dev_t (is_open store at device-mapper.c:213 lost the "
               "race vs remove reading is_open at dm_ioctl.c:354).\n");
        printf("Next dmstrategy (device-mapper.c:365) would dereference freed memory.\n");
    } else {
        printf("DF-1843: race not observed in this short harness run (kernel has "
               "a wider window across multiple CPUs with crit/serializer gaps).\n");
    }
    return 0;
}