DF-0904 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /* * 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; } |