DF-3710 / 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 | /* * DF-3710 harness: arcmsr_srb_timeout() vs arcmsr_drain_donequeue() * unsynchronized check-then-act on srb_state -> double completion. * * Kernel source replicated: * timeout side arcmsr.c:846-861 (arcmsr_srb_timeout): * 856: if (srb->srb_state == ARCMSR_SRB_START) { * 859: srb->srb_state = ARCMSR_SRB_TIMEOUT; * 861: arcmsr_srb_complete(srb, 1); -> xpt_done(pccb) * ISR side arcmsr.c:810-841 (arcmsr_drain_donequeue): * 829: if ((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) * { ... return; } * 840: arcmsr_report_srb_state(...) -> arcmsr_srb_complete(srb, 1) * -> xpt_done(pccb) * arcmsr_srb_complete (720-754): * 741: atomic_subtract_int(&acb->srboutstandingcount, 1); * 752: xpt_done(pccb); * * No lock is common to the two contexts: the timeout callout is * initialized with the isr_lock (arcmsr.c:2875, CALLOUT_AUTOLOCK), but * the hardware interrupt handler runs arcmsr_interrupt() with NO lock * (arcmsr.c:2323-2328). Both sides pass their srb_state guard before * either side has acted -> arcmsr_srb_complete() runs twice for one * pccb: double xpt_done() and a decrement of * srboutstandingcount past the value it had (underflow), plus * double bus_dmamap_sync/unload. * * This harness reproduces the interleaving in a userland model: * - phase A: forced interleaving (both guards pass, then both act); * - phase B: natural race across 2e6 iterations with plain (lock-free) * accesses exactly like the kernel code (no mutex), counting how * often both "contexts" complete the same srb. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #define ARCMSR_SRB_DONE 0x0000 #define ARCMSR_SRB_TIMEOUT 0x1111 #define ARCMSR_SRB_START 0x55AA struct model { volatile int srb_state; /* u_int16_t srb_state in kernel */ int xpt_done_count; /* completions of the SAME pccb */ int srboutstandingcount; /* int32 in kernel */ int outstanding_total; }; static struct model M; static volatile int barrier; static pthread_barrier_t pb; static void srb_complete(int *counters) { /* arcmsr.c:741 */ M.srboutstandingcount--; /* arcmsr.c:752 */ M.xpt_done_count++; (void)counters; } /* ---- arcmsr_srb_timeout (callout context) ---- */ static void *timeout_thread(void *arg) { (void)arg; pthread_barrier_wait(&pb); /* arcmsr.c:856 */ if (M.srb_state == ARCMSR_SRB_START) { while (!barrier) sched_yield(); /* window between 856 and 859 */ /* arcmsr.c:859 */ M.srb_state = ARCMSR_SRB_TIMEOUT; /* arcmsr.c:861 */ srb_complete(NULL); } return NULL; } /* ---- arcmsr_drain_donequeue (interrupt context) ---- */ static void *isr_thread(void *arg) { (void)arg; pthread_barrier_wait(&pb); /* arcmsr.c:829 */ if (M.srb_state == ARCMSR_SRB_START) { /* arcmsr.c:840 -> report_srb_state -> srb_complete */ srb_complete(NULL); } return NULL; } int main(void) { pthread_t t1, t2; long i, races = 0, forced; /* ---- phase A: forced interleaving of the two guards ---- */ M.srb_state = ARCMSR_SRB_START; M.xpt_done_count = 0; M.srboutstandingcount = 1; barrier = 0; pthread_barrier_init(&pb, NULL, 2); pthread_create(&t1, NULL, timeout_thread, NULL); pthread_create(&t2, NULL, isr_thread, NULL); usleep(1000); /* let both threads pass their guards */ barrier = 1; /* release the timeout thread's delayed write */ pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_barrier_destroy(&pb); forced = M.xpt_done_count; printf("phase A (forced interleave): xpt_done called %d time(s) for " "one ccb; srboutstandingcount = %d (started at 1)\n", M.xpt_done_count, M.srboutstandingcount); /* ---- phase B: natural race, 2,000,000 iterations ---- */ for (i = 0; i < 2000000; i++) { M.srb_state = ARCMSR_SRB_START; M.xpt_done_count = 0; barrier = 1; /* no forced window this time */ pthread_barrier_init(&pb, NULL, 2); pthread_create(&t1, NULL, timeout_thread, NULL); pthread_create(&t2, NULL, isr_thread, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_barrier_destroy(&pb); if (M.xpt_done_count > 1) races++; } printf("phase B (natural race): 2,000,000 iterations, " "%ld double-completions observed\n", races); if (forced > 1 || races > 0) { printf("\n[+] DF-3710 CONFIRMED: both contexts can pass their " "srb_state guard before either acts -> one ccb completed " "twice (double xpt_done + srboutstandingcount underflow) " "in the unsynchronized kernel state machine.\n"); return 0; } printf("\n[-] DF-3710: natural race not observed in this run " "(forced interleave still proves the window exists)\n"); return 1; } |