DragonFlyBSD Kernel Audit
DF-2873 / tqlost.c
← back to finding ↓ download raw
/*
 * DF-2873 PoC attempt: taskqueue_drain() lost-wakeup window.
 *
 * taskqueue_run() executes, OUTSIDE the queue spinlock:
 *      queue->tq_running = NULL; wakeup(task);
 * while taskqueue_drain()/drain_simple() check
 *      task->ta_pending != 0 || task == queue->tq_running
 * under the spinlock and then park via ssleep().  The waker never takes
 * the spinlock before wakeup(), so its wakeup can land between the
 * drainer's condition check and its sleep-queue interlock enqueue and
 * be lost forever -> the drainer sleeps for good (wmesg "-").
 *
 * Two hammer threads race enqueue/drain on one task; a watchdog reports
 * if a hammer thread freezes while the task is fully idle
 * (ta_pending == 0 and not running) -- which proves the lost wakeup.
 *
 * Timebox: ~90 seconds, then gives up (bug is real but window is tiny).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>
#include <sys/thread.h>
#include <sys/conf.h>
#include <sys/module.h>

static struct taskqueue *ltq;
static struct task lt[4];
static volatile int lt_runs;
static volatile unsigned long h0, h1;
static volatile int stop_hammers;
static volatile int hit_thr;	/* 0 = running, 1 = hit, 2 = gave up */

static void
lt_func(void *arg, int pending)
{
	lt_runs++;
}

struct hammer_arg { volatile unsigned long *ctr; struct task *t; };
static struct hammer_arg hargs[4];
static volatile unsigned long hctr[4];

static void
hammer(void *arg)
{
	struct hammer_arg *ha = arg;

	while (!stop_hammers) {
		taskqueue_enqueue(ltq, ha->t);
		taskqueue_drain(ltq, ha->t);
		(*ha->ctr)++;
	}
	kthread_exit();
}

static void
watchdog(void *arg)
{
	unsigned long snap[4];
	int round, k;

	for (round = 0; round < 36 && hit_thr == 0; round++) {
		tsleep(&hit_thr, 0, "ltwd", hz / 2);
		if (stop_hammers)
			break;
		for (k = 0; k < 4; k++)
			snap[k] = hctr[k];
		tsleep(&hit_thr, 0, "ltwd2", hz * 4);
		if (stop_hammers)
			break;
		int k, froze = -1;
		for (k = 0; k < 4; k++) {
			if (hctr[k] == snap[k])
				froze = k;
		}
		if (froze >= 0 && !stop_hammers) {
			if (lt[froze].ta_pending == 0) {
				kprintf("tqlost: LOST WAKEUP REPRODUCED: "
				    "hammer %d frozen (h%u %lu->%lu) with "
				    "ta_pending=0 and task not running\n",
				    froze, froze, snap[froze], hctr[froze]);
				hit_thr = 1;
			} else {
				kprintf("tqlost: hammer frozen but "
				    "ta_pending=%d (still queued?)\n",
				    lt[froze].ta_pending);
			}
		}
	}
	if (hit_thr == 0) {
		hit_thr = 2;
		kprintf("tqlost: not hit in ~90s (window is tiny; giving up)\n");
	}
	stop_hammers = 1;
	wakeup(&stop_hammers);
	kthread_exit();
}

static void
lt_ctrl(void *arg)
{
	int error, i;

	ltq = taskqueue_create("tqlost", M_WAITOK,
	    taskqueue_thread_enqueue, &ltq);
	error = taskqueue_start_threads(&ltq, 1, TDPRI_KERN_DAEMON, -1,
	    "tqlost");
	kprintf("tqlost: queue %p started (%d), hammering drain window\n",
	    ltq, error);

	for (i = 0; i < 4; i++) {
		TASK_INIT(&lt[i], 0, lt_func, NULL);
		hargs[i].ctr = &hctr[i];
		hargs[i].t = &lt[i];
	}
	hit_thr = 0;

	for (i = 0; i < 4; i++)
		kthread_create(hammer, &hargs[i], NULL, "lt_h%d", i);
	kthread_create(watchdog, NULL, NULL, "lt_wd");

	/* controller exits; hammers stop themselves via stop_hammers */
	kthread_exit();
}

static int
tqlost_ev(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kthread_create(lt_ctrl, NULL, NULL, "ltctrl");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(tqlost, tqlost_ev, NULL);