DF-5681 / race_once.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 | /* * DF-5681 โ race_once.c * * Demonstrate the *kernel* flavor of _PROP_ONCE_RUN (a cmpset-once with * no completion barrier, sys/libprop/prop_object_impl.h:302-303) * racing the lazy initialization of the interned-number registry in * sys/libprop/prop_number.c. * * The kernel macro is: * * #define _PROP_ONCE_DECL(x) static int x = 0; * #define _PROP_ONCE_RUN(x,f) if (atomic_cmpset_int(&(x), 0, 1)) f() * * The cmpset loser does NOT wait for the winner's f() to complete. * For prop_number that f() is _prop_number_init() (prop_number.c:169-176): * lockinit(&_prop_number_tree_mutex) + _prop_rb_tree_init(...). A loser * that fails the cmpset proceeds straight to lockmgr() on a lock that may * be mid-lockinit (lockinit wipes lk_count/lk_lockholder, * sys/kern/kern_lock.c:1300-1307 -> mutual exclusion broken) and then to * _prop_rb_tree_find() on a tree whose rbt_ops is still NULL (BSS zeroed) * -> prop_rb.c:90-93 dereferences rbt->rbt_ops->rbto_compare_key -> * NULL-pointer indirect call -> panic. * * This harness is linked against three library variants built by * build.sh from the guest's /usr/src/sys/libprop (byte-identical to the * audited sys/libprop): * * race_once -> stock sources. Userland _PROP_ONCE_RUN is * pthread_once (HAS completion semantics). CONTROL: * must survive. * race_once_ksem -> same sources with the userland once swapped for a * faithful port of the _KERNEL cmpset-once macro and * a 50ms window-widening delay inside * _prop_number_init (instrumentation only, after the * mutex init, before the tree init โ exactly the * window the kernel macro leaves open). * EXPECTED: SIGSEGV in _prop_rb_tree_find. * race_once_ksem warmup * -> same ksem library, but the harness completes the * once single-threaded before spawning the racing * threads. This is exactly what the proposed kernel * fix (SYSINIT(SI_SUB_PROP) bootstrap) does. * CONTROL for the fix: must survive. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <libprop/prop_number.h> #include <libprop/prop_object.h> #define NTHR 8 #define NVAL 512 static pthread_barrier_t bar; static void * worker(void *arg) { uintptr_t id = (uintptr_t)arg; prop_number_t keep[NVAL]; /* All threads make their FIRST-EVER prop_number call at the same * instant โ the kernel first-use window. */ pthread_barrier_wait(&bar); for (int i = 0; i < NVAL; i++) { prop_number_t a = prop_number_create_integer(i); prop_number_t b = prop_number_create_integer(i); if (a == NULL || b == NULL) { fprintf(stderr, "t%zu: alloc failed\n", (size_t)id); exit(2); } /* Interning invariant: one object per value. Broken mutex * or racing tree init shows up here as a duplicate. */ if (a != b) { fprintf(stderr, "t%zu: INTERNING VIOLATION at %d: %p != %p\n", (size_t)id, i, (void *)a, (void *)b); exit(3); } keep[i] = a; prop_object_release(b); } for (int i = 0; i < NVAL; i++) prop_object_release(keep[i]); printf("t%zu done\n", (size_t)id); return NULL; } int main(int argc, char **argv) { pthread_t th[NTHR]; int warmup = (argc > 1 && strcmp(argv[1], "warmup") == 0); if (warmup) { /* Emulates the SYSINIT(SI_SUB_PROP) bootstrap fix: * complete the lazy init single-threaded BEFORE any * concurrent first use. */ prop_number_t p0 = prop_number_create_integer(-1); prop_object_release(p0); printf("warmup: lazy init completed single-threaded\n"); } pthread_barrier_init(&bar, NULL, NTHR); for (uintptr_t i = 0; i < NTHR; i++) pthread_create(&th[i], NULL, worker, (void *)i); for (int i = 0; i < NTHR; i++) pthread_join(th[i], NULL); printf("SURVIVED: interning invariant held (%d values x %d threads)\n", NVAL, NTHR); return 0; } |