DF-0755 / race_harness.c
/* * DF-0755 - userspace replica of the unlocked tcp_debx race. * * The kernel code (sys/netinet/tcp_debug.c:84-95) does, with NO lock: * * static struct tcp_debug tcp_debug[TCP_NDEBUG]; // TCP_NDEBUG = 100 * static int tcp_debx; * ... * struct tcp_debug *td = &tcp_debug[tcp_debx++]; // line 84 * ... * if (tcp_debx == TCP_NDEBUG) // line 94 * tcp_debx = 0; // line 95 * * On SMP the load / add / store of `tcp_debx++` are three separate operations, * and the subsequent wrap check is a second non-atomic read/compare/store. * Two CPUs racing can both observe the same `tcp_debx`, both compute an in-range * `td`, both store `tcp_debx+1`, and -- critically -- one CPU can observe * `tcp_debx == TCP_NDEBUG` already-overflowed and compute `&tcp_debug[100]` * (one-past-end) BEFORE any wrap fires. Each lost update permanently advances * the index, so under sustained concurrent tracing the index runs away past the * array bound and subsequent traced packets write `struct tcp_debug` (dominated * by `td_cb = *tp`, hundreds of bytes) progressively further past the array * into BSS. * * This harness reproduces THAT EXACT C PATTERN with pthreads: N threads loop * doing `td = &arr[idx++]; if (idx == N) idx = 0;` with no synchronization, * and we record the maximum `idx` actually used to index the array. On a * real SMP box it routinely blows past TCP_NDEBUG-1, proving the * unlocked-increment-vs-array-bound race is real for this code shape. * * Build: cc -O2 -pthread -o race_harness race_harness.c * Run: ./race_harness */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <stdatomic.h> #define TCP_NDEBUG 100 #define NTHREADS 8 #define ITERS 2000000 /* Faithful replica of the kernel globals + indexing pattern. * We DO NOT use a lock here, exactly like the kernel. */ static struct { char pad[64]; } tcp_debug[TCP_NDEBUG]; static volatile int tcp_debx = 0; /* Recorded extremes (racy reads, but we only need to observe the blow-up). */ static volatile int max_idx_seen = 0; static volatile int oob_count = 0; static void * tracer(void *arg) { (void)arg; for (long i = 0; i < ITERS; i++) { /* exact kernel pattern: line 84 */ int slot = tcp_debx++; struct { char pad[64]; } *td = &tcp_debug[slot]; (void)td; /* in kernel: td->td_cb = *tp; etc -- a write */ if (slot > max_idx_seen) max_idx_seen = slot; if (slot >= TCP_NDEBUG) oob_count++; /* exact kernel pattern: lines 94-95 */ if (tcp_debx == TCP_NDEBUG) tcp_debx = 0; } return NULL; } int main(void) { pthread_t th[NTHREADS]; for (int i = 0; i < NTHREADS; i++) pthread_create(&th[i], NULL, tracer, NULL); for (int i = 0; i < NTHREADS; i++) pthread_join(th[i], NULL); printf("TCP_NDEBUG (array bound) = %d\n", TCP_NDEBUG); printf("max slot index used = %d\n", max_idx_seen); printf("OOB writes (slot>=100) = %d\n", oob_count); if (max_idx_seen >= TCP_NDEBUG) { printf("RESULT: RACE TRIGGERED -- index ran away past tcp_debug[] bound\n"); printf("=> the unlocked tcp_debx++ / wrap-check pattern is unsafe on SMP\n"); return 0; } else { printf("RESULT: no runaway observed this run (race did not land)\n"); return 1; } } |