DF-4653 / trigger.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-4653 trigger โ puc_setup_intr publishes ihand before ihandarg: * puc_intr can dispatch the child handler with a NULL * argument (kernel NULL-deref panic in siointr). * * VULNERABILITY: sys/dev/misc/puc/puc.c * puc_pci_attach() registers the shared IRQ handler very early: * :229-230 bus_alloc_resource(dev, SYS_RES_IRQ, ..., RF_ACTIVE|RF_SHAREABLE) * :236-238 BUS_SETUP_INTR(..., 0, puc_intr, sc, ..., NULL, NULL) * so puc_intr() runs on ANY event on that (shareable) line for the whole * attach sequence. Children register later, during * device_probe_and_attach() (:351) -> sio_attach() -> bus_setup_intr() * (sys/dev/serial/sio/sio.c:1236, INTR_MPSAFE, siointr, com). * * puc_setup_intr() publishes the handler in the WRONG ORDER: * :691 sc->sc_ports[i].ihand = ihand; <-- gate published FIRST * :692 sc->sc_ports[i].ihandarg = arg; <-- argument SECOND * * while puc_intr() gates exactly on that publication: * :407 if (sc->sc_ports[i].ihand != NULL && ...) * :409 (sc->sc_ports[i].ihand)(sc->sc_ports[i].ihandarg); * * An interrupt dispatched to puc_intr on another CPU (INTR_MPSAFE, no * serializer โ puc passes NULL at :238 and KKASSERTs serializer == NULL at * :693) between the two stores observes ihand != NULL with the stale * (NULL, bzero'd at :214) ihandarg and calls siointr(NULL), which * immediately dereferences it: sio.c:1750-1755 siointr(arg) -> * siointr1((struct com_s *)arg) -> first deref at sio.c:1797+. * Result: kernel NULL-deref panic (boot-time DoS). Because the IRQ * resource is RF_SHAREABLE, the triggering event can come from ANY * sibling device sharing the line, not only from the puc card itself. * * The plain stores carry no compiler barriers, so the compiler may also * sink/swap them; the teardown side (:711-712) has the correct store * order (gate cleared first) but is equally barrier-less (and dead in * practice: no child driver in the tree implements detach). * * This model reproduces the publication-order defect as a userspace * interleaving of the exact kernel store/load pattern (thread A = CPU0 * running puc_setup_intr, thread B = CPU1 running puc_intr on a shared-IRQ * event). Synchronization is used only to OPEN the window each round โ * the kernel defect is precisely that none exists. Three phases: * * A. kernel store order, back-to-back stores โ the faithful model * B. kernel store order, 1-sched_yield() widened between the stores * (instrumentation only; proves the ORDER is the defect) * C. FIXED store order (arg first, gate last), same widening as B * โ must yield ZERO NULL-argument dispatches * * B >> 0 hits while C == 0 hits proves the ordering defect; A > 0 shows it * is observable even at faithful (2-store) width. */ #include <stdio.h> #include <sched.h> #include <pthread.h> struct port { /* mirrors sc_ports[i] (puc.c:113-118) */ void (*ihand)(void *); void *ihandarg; }; static struct port port; static pthread_barrier_t bar; static unsigned long dispatched; /* calls of ihand() */ static unsigned long null_arg; /* == kernel panic condition */ static void siointr_model(void *arg) /* siointr(): siointr1(deref arg) */ { /* struct com_s *com = arg; siointr1(com) -> com->... -> PANIC if NULL */ if (arg == NULL) null_arg++; /* in the kernel: NULL-deref right here */ } static void reset_port(void) /* fresh port, bzero'd at puc.c:214 */ { port.ihand = NULL; port.ihandarg = NULL; } static void *cpu0_kernel_order(void *unused) /* puc_setup_intr :691-692 */ { int i; (void)unused; for (i = 0; i < 200000; i++) { reset_port(); pthread_barrier_wait(&bar); /* IRQ live from :236 on */ /* ---- kernel store order, verbatim ---- */ port.ihand = siointr_model; /* :691 FIRST (gate) */ port.ihandarg = (void *)0x1234; /* :692 SECOND (arg) */ pthread_barrier_wait(&bar); } return NULL; } static void *cpu0_kernel_order_widened(void *unused) { int i; (void)unused; for (i = 0; i < 200000; i++) { reset_port(); pthread_barrier_wait(&bar); port.ihand = siointr_model; /* :691 FIRST (gate) */ sched_yield(); /* instrumentation */ port.ihandarg = (void *)0x1234; /* :692 SECOND (arg) */ pthread_barrier_wait(&bar); } return NULL; } static void *cpu0_fixed_order_widened(void *unused) /* the fix.diff order */ { int i; (void)unused; for (i = 0; i < 200000; i++) { reset_port(); pthread_barrier_wait(&bar); port.ihandarg = (void *)0x1234; /* argument FIRST */ sched_yield(); /* same widening */ port.ihand = siointr_model; /* gate LAST */ pthread_barrier_wait(&bar); } return NULL; } static void *cpu1_puc_intr(void *unused) /* puc_intr :406-409 */ { int i; (void)unused; for (i = 0; i < 200000; i++) { pthread_barrier_wait(&bar); /* ---- kernel load pattern, verbatim ---- */ if (port.ihand != NULL) { /* :407 gate */ void *a = port.ihandarg; /* :409 load arg */ port.ihand(a); /* (ihand)(ihandarg) */ dispatched++; } pthread_barrier_wait(&bar); } return NULL; } static void run_phase(const char *name, void *(*cpu0)(void *)) { pthread_t a, b; dispatched = 0; null_arg = 0; pthread_barrier_init(&bar, NULL, 2); pthread_create(&a, NULL, cpu0, NULL); pthread_create(&b, NULL, cpu1_puc_intr, NULL); pthread_join(a, NULL); pthread_join(b, NULL); pthread_barrier_destroy(&bar); printf("%-44s dispatches=%-8lu NULL-arg dispatches=%lu%s\n", name, dispatched, null_arg, null_arg ? " <-- kernel: siointr(NULL) panic" : ""); } int main(void) { printf("DF-4653 interleaving model: puc_setup_intr (puc.c:691-692) vs puc_intr (puc.c:406-409)\n\n"); run_phase("A. kernel order, faithful 2-store window:", cpu0_kernel_order); run_phase("B. kernel order, widened (yield between):", cpu0_kernel_order_widened); run_phase("C. FIXED order (arg first), same widen :", cpu0_fixed_order_widened); printf("\nB>0 while C==0 proves the publication order is the defect.\n"); return 0; } |