DragonFlyBSD Kernel Audit
DF-0755 / race_harness.c
← back to finding ↓ download raw
/*
 * 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;
	}
}