โฌข DragonFlyBSD Kernel Audit
DF-4276 / df4276_rep.c
โ† back to finding โ†“ download raw
/*
 * DF-4276 protocol replica โ€” the cbb_detach()/cbb_event_thread() teardown
 * races in sys/dev/pccard/pccbb/pccbb.c.
 *
 * Replicated protocol (verbatim lock/sleep discipline of the driver):
 *
 *   cbb_event_thread (pccbb.c:423-489)      cbb_detach (pccbb.c:279-306)
 *   -------------------------------------  --------------------------------
 *   sc->flags |= CBB_KTHREAD_RUNNING;      device_delete_children(brdev);
 *   while ((sc->flags & DONE) == 0) {      bus_teardown_intr(...);
 *       ...cbb_insert/cbb_removal...       sc->flags |= CBB_KTHREAD_DONE;
 *       tsleep(&generic_cv, ...);          if (flags & RUNNING) {
 *   }                                          crit_enter();
 *   sc->flags &= ~RUNNING;                     wakeup(&generic_cv);
 *   wakeup(sc->event_thread);                  tsleep(sc->event_thread,
 *   [thread exits]                                 0, "cbbun", 0);
 *                                               crit_exit();
 *                                           }
 *
 * The window under test: between cbb_detach()'s wakeup(&sc->generic_cv)
 * (pccbb.c:297) and its tsleep(sc->event_thread,...) (pccbb.c:298) there is
 * NO interlock. If the event thread runs its tail โ€” wakes from generic_cv,
 * observes DONE, clears RUNNING, executes wakeup(sc->event_thread) โ€” while
 * the detacher is inside that window (preempted / running concurrently on
 * another CPU), the final wakeup() finds nobody on the sleep queue and is
 * lost. DFly wakeups are not queued; the detacher then sleeps on the ident
 * with timeout 0 == forever -> kldunload/detach hangs permanently.
 *
 * (A crit section does not close the window: it is per-CPU and does not
 * stop the event thread running on another CPU, nor preemption between the
 * two calls in the detacher itself.)
 *
 * This replica forces exactly that interleaving deterministically: the
 * "gap" flag stands in for the preemption/scheduling that opens the window
 * in the wild. Everything else is the driver's own discipline.
 *
 * Companion races in the same teardown (source-traced, see VERDICT.md):
 *   - non-atomic RMW on sc->flags can lose CBB_KTHREAD_DONE;
 *   - device_delete_children() runs BEFORE the thread is stopped, so the
 *     thread can call cbb_insert() -> CARD_ATTACH_CARD(sc->cbdev) /
 *     exca_insert(&sc->exca[0]) on already-deleted children.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/kthread.h>
#include <sys/proc.h>
#include <sys/serialize.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/mplock2.h>
#include <machine/clock.h>

#define	RUNNING_FLAG	0x40000000	/* CBB_KTHREAD_RUNNING */
#define	DONE_FLAG	0x80000000	/* CBB_KTHREAD_DONE    */

static uint32_t flags;			/* sc->flags analog          */
static int generic_cv;			/* sc->generic_cv analog     */
static struct thread *event_td;		/* sc->event_thread analog   */
static struct thread *detach_td;
static volatile int gap;		/* forces the unguarded window */
static volatile int done;

/* ---- cbb_event_thread tail analog ---- */
static void
event_thread(void *arg)
{
	get_mplock();
	flags |= RUNNING_FLAG;
	while ((flags & DONE_FLAG) == 0) {
		crit_enter();
		tsleep(&generic_cv, 0, "cbbgcv", 0);	/* pccbb.c:479 */
		crit_exit();
	}
	/* Park until the detacher is inside its wakeup->tsleep window. */
	while (!gap)
		tsleep(&gap, 0, "df4276w", 1);

	flags &= ~RUNNING_FLAG;		/* pccbb.c:486 (non-atomic RMW) */
	wakeup(event_td);		/* pccbb.c:487 โ€” the final wakeup */
	kprintf("df4276: event thread tail complete; final wakeup() "
	    "delivered into the void (detacher not yet asleep)\n");
	rel_mplock();
	kthread_exit();
}

/* ---- cbb_detach analog ---- */
static void
detach_thread(void *arg)
{
	get_mplock();
	detach_td = curthread;

	flags |= DONE_FLAG;				/* pccbb.c:294 */
	if (flags & RUNNING_FLAG) {
		crit_enter();
		wakeup(&generic_cv);			/* pccbb.c:297 */
		gap = 1;		/* window open (preemption point) */
		DELAY(200000);	/* detacher preempted inside window */
		tsleep(event_td, 0, "cbbun", 0);	/* pccbb.c:298 */
		crit_exit();
	}
	kprintf("df4276: detach COMPLETED -- no lost wakeup\n");
	done = 1;
	wakeup(&done);
	rel_mplock();
	kthread_exit();
}

static void
reporter(void *arg)
{
	int i;

	get_mplock();
	for (i = 0; i < 5 && !done; i++) {
		tsleep(&done, 0, "df4276r", hz);
		kprintf("df4276: detach_td=%p wchan=%p wmesg=\"%s\"\n",
		    detach_td, detach_td->td_wchan,
		    detach_td->td_wmesg ? detach_td->td_wmesg : "(null)");
	}
	if (done)
		kprintf("df4276: RESULT: no lost wakeup\n");
	else
		kprintf("df4276: RESULT: LOST WAKEUP REPRODUCED -- detacher "
		    "asleep forever in \"%s\"; in the driver this is "
		    "cbb_detach()/kldunload hung permanently\n",
		    detach_td->td_wmesg ? detach_td->td_wmesg : "?");
	rel_mplock();
	kthread_exit();
}

static int
df4276_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kthread_create(event_thread, NULL, &event_td, "df4276ev");
		kthread_create(detach_thread, NULL, NULL, "df4276det");
		kthread_create(reporter, NULL, NULL, "df4276rep");
		kprintf("df4276: replica armed\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df4276rep_mod = {
	"df4276rep",
	df4276_load,
	NULL
};

DECLARE_MODULE(df4276rep, df4276rep_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df4276rep, 1);