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