DF-3727 / 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | /* * DF-3727 harness โ ciss_disable_adapter() operates on an UNINITIALIZED * STAILQ head (sys/dev/raid/ciss/ciss.c:3465) โ the first * ciss_enqueue_complete(cr, &qh) (== STAILQ_INSERT_TAIL, sys/sys/queue.h:306) * executes `*(head)->stqh_last = (elm)` through whatever garbage the stack * slot holds => wild kernel write of a kernel heap pointer (the ciss_request), * and the later ciss_dequeue_complete() dereferences the equally-garbage * stqh_first => wild read. * * DragonFly divergence: FreeBSD's ciss.c ciss_disable_adapter() has * STAILQ_INIT(&qh); (upstream ciss.c:3581) โ DragonFly's copy is missing it. * * This userspace replica proves the primitive with the EXACT queue.h macro * text and a stack-grooming step (a prior call paints the stack region the * callee's uninitialized `qh` will land on with a chosen address โ the * kernel-equivalent of prior callout/syscall activity leaving a controlled * value in that stack slot). If the first enqueue writes the request pointer * to the groomed (attacker-chosen) address, the wild-write primitive is proven. * * Build: cc -O2 -Wall -o harness harness.c (build.sh also builds -O0) * Run: ./harness * Fixed: cc -DFIXED ... exercises the STAILQ_INIT(&qh) patch (fix.diff). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> /* ---- exact replica of DragonFly sys/sys/queue.h STAILQ (lines 288-291, 306-310) ---- */ struct ciss_request_rep { struct ciss_request_rep *cr_link; /* STAILQ_ENTRY(ciss_request) cr_link */ int cr_tag; }; typedef struct cr_qhead { /* typedef STAILQ_HEAD(, ciss_request) */ struct ciss_request_rep *stqh_first; struct ciss_request_rep **stqh_last; } cr_qhead_t; #define STAILQ_INIT_REPLICA(head) do { \ (head)->stqh_first = NULL; \ (head)->stqh_last = &(head)->stqh_first; \ } while (0) /* sys/sys/queue.h:306-310 โ NOTE: no NULL guard on stqh_last */ #define STAILQ_INSERT_TAIL_REPLICA(head, elm) do { \ (elm)->cr_link = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &(elm)->cr_link; \ } while (0) /* watch page at a fixed "attacker-chosen" address */ #define WATCH_ADDR ((struct ciss_request_rep **)0x51000000UL) #define WATCH_PAGE ((void *)0x51000000UL) static struct ciss_request_rep reqs[4]; /* * Stack groomer: paints 64 longs (512 bytes) of its stack frame with * WATCH_ADDR, then returns. The immediately-following call at the same stack * depth reuses this painted region for its own locals โ including the * uninitialized `qh`. */ static void __attribute__((noinline)) groom(void) { volatile unsigned long paint[64]; int i; for (i = 0; i < 64; i++) paint[i] = (uintptr_t)WATCH_ADDR; /* force paint onto the stack */ if (paint[63] == 0xdeadbeefUL) printf("(groom oddity)\n"); } /* * Faithful replica of ciss_disable_adapter() (ciss.c:3462-3507), reduced to * the queue manipulation. `nbusy` = number of BUSY requests found. */ static int __attribute__((noinline)) ciss_disable_adapter_rep(int nbusy) { cr_qhead_t qh; /* ciss.c:3465 โ NO STAILQ_INIT */ struct ciss_request_rep *cr; int i, wild = 0; void *observed_last; void *observed_first; #ifdef FIXED STAILQ_INIT_REPLICA(&qh); /* the fix (FreeBSD ciss.c:3581) */ #endif /* ciss.c:3475-3484 */ for (i = 0; i < nbusy; i++) { cr = &reqs[i]; observed_last = (i == 0) ? (void *)qh.stqh_last : NULL; if (i == 0 && qh.stqh_last == WATCH_ADDR) wild = 1; /* the uninitialized slot holds the groomed ptr */ /* ciss.c:3483: ciss_enqueue_complete(cr, &qh) */ STAILQ_INSERT_TAIL_REPLICA(&qh, cr); } observed_first = (void *)qh.stqh_first; /* ciss.c:3487 dequeue reads this */ /* ciss.c:3486-3488: ciss_dequeue_complete(sc, &qh) โ reads stqh_first * (garbage if list never initialized properly) */ printf(" first-enqueue stqh_last = %p (groomed target %p)%s\n", observed_last, (void *)WATCH_ADDR, wild ? " <== UNINITIALIZED STACK SLOT" : ""); printf(" post-enqueue stqh_first = %p (list integrity %s)\n", observed_first, (qh.stqh_first == &reqs[0] || qh.stqh_first == NULL) ? "ok" : "BROKEN (garbage head)"); (void)observed_first; return wild; } int main(void) { void *page; int i, wild_hits = 0, runs = 3; page = mmap(WATCH_PAGE, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); if (page != WATCH_PAGE) { perror("mmap watch page"); return 2; } memset(page, 0, 4096); for (i = 0; i < 4; i++) reqs[i].cr_tag = i + 1; printf("== DF-3727 ciss_disable_adapter uninitialized STAILQ head ==\n"); printf("scenario: adapter heartbeat failed 3x (ciss.c:3389-3398) with 2 BUSY\n" "requests; ciss_periodic -> ciss_disable_adapter (ciss.c:3395);\n" "stack slot holding qh.stqh_last groomed to %p by prior activity\n\n", (void *)WATCH_ADDR); for (i = 0; i < runs; i++) { printf("run %d:\n", i + 1); memset(page, 0, 4096); /* clear watch page */ groom(); /* paint the stack */ if (ciss_disable_adapter_rep(2)) wild_hits++; /* did the first enqueue write a request pointer at WATCH_ADDR? */ if (*(struct ciss_request_rep **)WATCH_PAGE == &reqs[0]) { printf(" [WILD WRITE] *(%p) = %p (kernel: request ptr)\n", (void *)WATCH_ADDR, (void *)&reqs[0]); } } printf("\n"); if (wild_hits > 0 && *(struct ciss_request_rep **)WATCH_PAGE == &reqs[0]) { printf("[BUG REPRODUCED] %d/%d runs: STAILQ_INSERT_TAIL wrote the\n" "request pointer THROUGH the uninitialized stqh_last to the\n" "attacker-groomed address %p. In the kernel this is an\n" "uncontrolled write of a kernel heap pointer to a\n" "stack-garbage-controlled address (CWE-457 -> CWE-787).\n", wild_hits, runs, (void *)WATCH_ADDR); return 0; } #ifdef FIXED printf("[FIXED] STAILQ_INIT(&qh) present: no write through garbage,\n" "list head consistent, no watch-page hit.\n"); return 0; #else printf("[NOT REPRODUCED] grooming did not land on the qh slot in this\n" "build (stack layout shifted); see harness.O0 (unoptimized build).\n"); return 1; #endif } |