DF-5693 / race_bool.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 | /* * DF-5693 — race_bool.c * * Differential harness for the *kernel* flavor of _PROP_ONCE_RUN racing * the lazy singleton init in sys/libprop/prop_bool.c. * * The kernel macro (sys/libprop/prop_object_impl.h:302-303) 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 completion flag flips to 1 BEFORE f() runs, and the cmpset loser * does NOT wait. For prop_bool the raced f() is _prop_bool_init() * (prop_bool.c:111-124), which stores po_type and pb_value into the two * BSS singletons _prop_bool_true / _prop_bool_false (prop_bool.c:40-41). * There is NO boot-time kernel consumer of prop_bool (the only in-kernel * reachability is plist internalize of <true/>/<false/> via ioctls such * as device-mapper), so the first-ever use is attacker-triggerable and * the cmpset window is live at that moment. * * What the loser can observe (with a widened window): * state A: po_type == NULL, pb_value == false * -> prop_bool_true(&_prop_bool_true) guard fails => FALSE * prop_bool_copy(b) guard fails => NULL * prop_object_release(b) => po->po_type->pot_lock * (prop_object.c:1085) => NULL-pointer read => SIGSEGV * state B: po_type set, pb_value still false (between lines 116/117) * -> object *looks* typed but prop_bool_true(b) == false: * the TRUE singleton lies about its value. * * Why this is filed Info and not higher: at REALISTIC timing the window * is a handful of stores and the loser's in-window path * (_prop_bool_alloc body, prop_bool.c:132-135 -> prop_object_retain -> * atomic_inc_32_nv on po_refcnt, prop_object.c:987-994) reads NO * initialized field; every po_type/pb_value read happens long after the * winner's stores became globally visible. Section 6 of the battery * (300 fresh processes, no widening) demonstrates exactly that: 0/300 * anomalies. The widening knobs are rig instrumentation only. * * Library variants (build.sh): * race_bool -> stock /usr/src/sys/libprop (userland once is * pthread_once, HAS completion semantics). CONTROL. * race_bool_ksem -> same sources, userland once swapped for a faithful * port of the _KERNEL cmpset-once + env-controlled * window widening inside _prop_bool_init. * * Modes: [check] default: create both singletons, verify the * value/identity/copy invariants, then release. * release skip checks, create + release immediately (crash * flavor: NULL po_type -> SIGSEGV in release). * warmup complete the once single-threaded first (= the * SYSINIT(SI_SUB_PROP) bootstrap of fix.diff). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <libprop/prop_bool.h> #include <libprop/prop_object.h> #define NTHR 8 static pthread_barrier_t bar; static int mode_release; static int violations; static void report(const char *what, const char *state) { fprintf(stderr, "VIOLATION[%s]: %s\n", state, what); violations = 1; } static void * worker(void *arg) { prop_bool_t b, f, bc; pthread_barrier_wait(&bar); /* first-ever prop_bool use, all at once */ b = prop_bool_create(true); f = prop_bool_create(false); if (mode_release) { /* crash flavor: release while the winner may still be * inside _prop_bool_init (state A) -> po_type NULL -> * SIGSEGV at prop_object.c:1085 (po->po_type->pot_lock). */ prop_object_release(b); prop_object_release(f); return (NULL); } if (b == NULL || f == NULL || b == f) { report("create returned NULL or both singletons identical", "A:po_type=NULL"); goto out; } if (prop_bool_true(b) != true) { /* distinguish mid-init states for the log */ if (prop_bool_copy(b) == NULL) report("prop_bool_true(_prop_bool_true) == false " "(guard failed: po_type not yet stored)", "A:po_type=NULL"); else report("prop_bool_true(_prop_bool_true) == false " "(typed object, pb_value stale)", "B:value=stale"); goto out; } if (prop_bool_true(f) != false) { report("prop_bool_true(_prop_bool_false) == true", "B:value=stale"); goto out; } bc = prop_bool_copy(b); if (bc == NULL) { report("prop_bool_copy failed (guard)", "A:po_type=NULL"); goto out; } if (bc != b || !prop_bool_equals(b, bc)) { report("singleton identity broken", "A/B"); prop_object_release(bc); goto out; } prop_object_release(bc); out: prop_object_release(b); prop_object_release(f); return (NULL); } int main(int argc, char **argv) { pthread_t th[NTHR]; int warmup = 0, i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "warmup") == 0) warmup = 1; else if (strcmp(argv[i], "release") == 0) mode_release = 1; } if (warmup) { /* Emulates fix.diff: SYSINIT(SI_SUB_PROP) completes the lazy * init single-threaded BEFORE any concurrent first use. */ prop_bool_t p0 = prop_bool_create(true); prop_object_release(p0); printf("warmup: lazy init completed single-threaded\n"); } pthread_barrier_init(&bar, NULL, NTHR); for (i = 0; i < NTHR; i++) pthread_create(&th[i], NULL, worker, NULL); for (i = 0; i < NTHR; i++) pthread_join(th[i], NULL); if (violations) { fprintf(stderr, "FAILED: singleton init invariant broken\n"); return (3); } printf("SURVIVED: both singletons typed, values correct, " "identity held (%d threads)\n", NTHR); return (0); } |