โฌข DragonFlyBSD Kernel Audit
DF-0904 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-0904 โ€” deterministic code-level harness for the
 * hammer2_update_spans() infinite-loop defect.
 *
 * The bug (sys/vfs/hammer2/hammer2_iocom.c:313-341):
 *
 *   while (chain) {
 *       if (chain->bref.type != HAMMER2_BREF_TYPE_INODE)
 *           continue;                                  // <-- BUG
 *       ...
 *       chain = hammer2_chain_next(&parent, chain, ...);  // ONLY cursor advance
 *   }
 *
 * The `continue` at line 315 skips the only cursor advance at lines 338-340,
 * so any non-INODE chain (INDIRECT/DATA/etc., e.g. from a crafted image)
 * pins the loop forever at 100% CPU holding iroot+parent+chain locks
 * => kernel-thread spin / cluster-path deadlock (DoS).
 *
 * The verified-correct sibling is hammer2_pfslocate (hammer2_vfsops.c:1553-1566)
 * which places `chain = hammer2_chain_next(...)` OUTSIDE the type dispatch,
 * so the cursor advances regardless of chain type.
 *
 * This harness replicates the loop structure byte-for-byte (with abstracted
 * chain cursor + iteration cap so it terminates for measurement) and shows:
 *   - the BUGGY control flow spins forever on a non-inode entry (hit cap)
 *   - the FIXED control flow (chain_next pulled out of the branch, matching
 *     the vfsops.c sibling) terminates cleanly
 *
 * Build: cc -O2 -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ---- abstract the hammer2 chain cursor just enough to model the loop ---- */

#define HAMMER2_BREF_TYPE_INODE   0x02   /* matches sys/vfs/hammer2/hammer2_disk.h */
#define HAMMER2_BREF_TYPE_INDIRECT 0x01
#define HAMMER2_BREF_TYPE_DATA    0x10

struct hammer2_bref {
    uint8_t  type;
};
struct hammer2_chain {
    struct hammer2_bref bref;
    const char          *tag;     /* for tracing */
};

/* A tiny linear "media" under the super-root: two INODE PFS labels followed
 * by a non-INODE (e.g. INDIRECT) chain โ€” exactly the degenerate state a
 * crafted/corrupted image presents to hammer2_update_spans().
 */
static struct hammer2_chain media[] = {
    { { HAMMER2_BREF_TYPE_INODE },   "PFS@00" },
    { { HAMMER2_BREF_TYPE_INODE },   "PFS@01" },
    { { HAMMER2_BREF_TYPE_INDIRECT },"INDIRECT@02" },  /* trips the bug */
    { { HAMMER2_BREF_TYPE_INODE },   "PFS@03" },
    { { 0 },                         NULL },           /* terminator */
};

/* Model of hammer2_chain_lookup/next: returns the chain at offset *cursor
 * and advances the cursor; returns NULL at terminator. */
static struct hammer2_chain *
chain_next(int *cursor)
{
    struct hammer2_chain *c = &media[*cursor];
    if (c->tag == NULL) return NULL;
    (*cursor)++;
    return c;
}

/* Iteration cap = analog of a watchdog. The real kernel loop has NO cap;
 * we use it only so the harness terminates to report the spin. */
#define CAP 100000

/* ---------------------- BUGGY loop (verbatim structure) ------------------ */
static int
run_buggy(int *iters)
{
    int cursor = 0;
    int n = 0;
    struct hammer2_chain *chain = chain_next(&cursor);   /* lookup */
    *iters = 0;
    while (chain) {
        (*iters)++;
        if ((*iters) > CAP) return -1;                   /* watchdog */
        if (chain->bref.type != HAMMER2_BREF_TYPE_INODE)
            continue;                                    /* <-- skips advance */
        /* (process PFS label โ€” modeled) */
        n++;
        chain = chain_next(&cursor);                      /* ONLY advance */
    }
    return n;
}

/* ---------------------- FIXED loop (mirrors vfsops.c sibling) ------------ *
 * Pull `chain = chain_next(...)` out of the type branch so the cursor
 * always advances โ€” exactly the structure at hammer2_vfsops.c:1553-1566.   */
static int
run_fixed(int *iters)
{
    int cursor = 0;
    int n = 0;
    struct hammer2_chain *chain = chain_next(&cursor);   /* lookup */
    *iters = 0;
    while (chain) {
        (*iters)++;
        if (chain->bref.type != HAMMER2_BREF_TYPE_INODE) {
            /* would kprintf("Non inode chain type ... under super-root"); */
        } else {
            n++;
        }
        chain = chain_next(&cursor);                      /* always advances */
    }
    return n;
}

int
main(void)
{
    int iters, rc;

    printf("DF-0904 hammer2_update_spans loop-structure harness\n");
    printf("media topology under spmp->iroot:\n");
    for (int i = 0; media[i].tag; i++)
        printf("  [%d] type=0x%02x %s\n", i, media[i].bref.type, media[i].tag);

    printf("\nbuggy loop (iocom.c:313-341 verbatim, with watchdog):\n");
    rc = run_buggy(&iters);
    if (rc < 0)
        printf("  RESULT: INFINITE LOOP โ€” watchdog tripped at %d iters "
               "(cursor stuck on the non-inode chain)\n", iters);
    else
        printf("  RESULT: terminated, processed %d PFS labels in %d iters\n",
               rc, iters);

    printf("\nfixed loop (mirrors vfsops.c:1553-1566 sibling):\n");
    rc = run_fixed(&iters);
    printf("  RESULT: terminated, processed %d PFS labels in %d iters\n",
           rc, iters);

    printf("\nverdict: ");
    int buggy_loops = (run_buggy(&iters) < 0);
    if (buggy_loops)
        printf("BUGGY loop spins forever on a non-inode chain; "
               "FIXED loop terminates normally => bug confirmed at code level.\n");
    else
        printf("control flow mismatch โ€” investigate.\n");
    return buggy_loops ? 0 : 1;
}