โฌข DragonFlyBSD Kernel Audit
DF-0819 / hammer_blkmap_trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-0819 runtime trigger โ€” attempt to deadlock a mounted HAMMER filesystem
 * by hitting the hammer_bnew error path under memory pressure / ENOSPC.
 *
 * This must be run as root (HAMMER mount requires root). It:
 *   1. Creates a small hammer image (mdconfig)
 *   2. newfs_hammer on it
 *   3. Mounts it
 *   4. Fills it to near-ENOSPC with small files
 *   5. Concurrently mmaps+memsets large anonymous regions (memory pressure)
 *      while writing 16K-aligned blocks to the hammer fs
 *   6. Tries to trigger the hammer_bnew failure -> blkmap_lock leak
 *
 * If the filesystem DEADLOCKS (all further writes hang in D-state),
 * the bug is triggered.  If writes return ENOSPC, the error path was
 * not reached (expected โ€” hammer_io_new always returns 0).
 *
 * Usage: ./hammer_blkmap_trigger <mountpoint>
 *        (root must set up the hammer image and mount it first)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>

static volatile int g_running = 1;

static void alarm_handler(int sig) { g_running = 0; }

int main(int argc, char **argv)
{
    const char *mp;
    char path[512];
    int fd, i, written = 0, errors = 0;
    char buf[16384]; /* 16K โ€” HAMMER_BUFSIZE, to hit buffer-boundary alloc */

    if (argc < 2) {
        fprintf(stderr, "usage: %s <hammer_mountpoint>\n", argv[0]);
        return 2;
    }
    mp = argv[1];
    memset(buf, 'A', sizeof(buf));

    /* Child: memory pressure via mmap+memset */
    pid_t memchild = fork();
    if (memchild == 0) {
        size_t total = 0;
        for (;;) {
            void *p = mmap(NULL, 64*1024*1024, PROT_READ|PROT_WRITE,
                           MAP_PRIVATE|MAP_ANON, -1, 0);
            if (p == MAP_FAILED) {
                /* system out of memory โ€” keep trying to maintain pressure */
                usleep(1000);
                continue;
            }
            memset(p, 0xAB, 64*1024*1024);
            total += 64*1024*1024;
            /* don't unmap โ€” keep memory consumed */
            if (total > (size_t)3*1024*1024*1024)
                break; /* 3 GB consumed */
        }
        /* spin to keep pressure on */
        for (;;) pause();
        _exit(0);
    }

    /* Parent: hammer writes to near-full filesystem */
    signal(SIGALRM, alarm_handler);
    alarm(15); /* 15 second budget */

    for (i = 0; g_running && i < 100000; i++) {
        snprintf(path, sizeof(path), "%s/stress_%06d", mp, i);
        fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
        if (fd < 0) {
            if (errno == ENOSPC) {
                errors++;
                continue; /* expected when fs is full */
            }
            errors++;
            if (i > 100 && errors > 1000) {
                /* If errors are not ENOSPC, something else is wrong */
                break;
            }
            continue;
        }
        /* write a few 16K blocks to trigger buffer-boundary allocation */
        int j;
        for (j = 0; j < 4; j++) {
            ssize_t n = write(fd, buf, sizeof(buf));
            if (n < 0) {
                if (errno != ENOSPC)
                    errors++;
                break;
            }
            written++;
        }
        close(fd);
        /* unlink periodically to avoid filling with too many inodes */
        if (i % 100 == 0)
            unlink(path);
    }

    /* Check if filesystem is still responsive */
    snprintf(path, sizeof(path), "%s/.responsive_check", mp);
    alarm(5);
    fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
    if (fd >= 0) {
        write(fd, "ok", 2);
        close(fd);
        unlink(path);
        printf("RESULT: filesystem still responsive after %d writes, %d errors\n",
               written, errors);
        printf("(hammer_bnew error path NOT hit โ€” lock leak NOT triggered)\n");
        printf("This confirms the error path is effectively dead code.\n");
    } else {
        printf("RESULT: filesystem UNRESPONSIVE (open returned %d: %s)\n",
               errno, strerror(errno));
        printf("Possible deadlock โ€” blkmap_lock may be leaked!\n");
    }

    kill(memchild, SIGTERM);
    waitpid(memchild, NULL, 0);
    return 0;
}