โฌข DragonFlyBSD Kernel Audit
DF-5017 / df5017.c
โ† back to finding โ†“ download raw
/*
 * DF-5017 โ€” dma_fence_wait_any_timeout defect demonstration (DragonFly KLD)
 *
 * Proves, against the LIVE drm.ko symbols on an unmodified guest kernel:
 *
 *   TEST A  clean-timeout wrap: wait_any on an unsignaled fence with a small
 *           finite timeout returns NEGATIVE (contract: 0) โ€” the unsigned
 *           `end - jiffies` wrap DF-2046 documented for default_wait,
 *           duplicated in this different function (linux_fence.c:206-215).
 *
 *   TEST B  MAX_SCHEDULE_TIMEOUT (intr=false, amdgpu_sa.c:322 style):
 *           returns positive after the signal (no hang) โ€” but only because
 *           the long->int truncation of `timo' turns the "infinite" wait
 *           into a poll loop (see TEST E).
 *
 *   TEST C  lost-wakeup primitive: a wakeup() fired between a waiter's
 *           condition check and its tsleep() interlock is DROPPED by
 *           DragonFly (no latch) โ€” exactly the window at
 *           linux_fence.c:208->210 where no lock is held.
 *
 *   TEST E  the truncated timo: tsleep(ident, 0, ..., (int)LONG_MAX) never
 *           sleeps forever โ€” the negative tick count is clamped to fire on
 *           the next tick by _callout_setup_quick (kern_timeout.c:968-970),
 *           i.e. what wait_any passes every loop iteration for
 *           MAX_SCHEDULE_TIMEOUT => 100 wakeups/sec poll loop.
 *
 *   TEST A' / B'  the same scenarios through a FIXED copy of
 *           wait_any_timeout using the set_current_state() +
 *           schedule_timeout() idiom the DF shim provides (sched.h:118-190)
 *           โ€” the latching state machine closes the lost-wake window and
 *           schedule_timeout() returns non-negative remaining time.
 *
 * Build:  see Makefile (kldload drm first)
 * Run:    kldload ./df5017.ko ; watch console/dmesg
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/kthread.h>
#include <sys/lock.h>

#include <linux/dma-fence.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>

/* ------------------------------------------------------------------ */
/* shared test state                                                   */

static const struct dma_fence_ops fx_ops;	/* all NULL */

struct fx_state {
	struct dma_fence	fence;
	spinlock_t		lock;
	volatile int		phase;	/* 1 = signal in 3s, 2 = done, -1 = exit */
};

static struct fx_state	fx;

/* signaler kthread for TEST B / B' */
static void
fx_signaler(void *arg)
{
	struct fx_state *s = arg;

	for (;;) {
		tsleep(&s->phase, 0, "fxsig", hz);
		if (s->phase == 1) {
			tsleep(&s->fence, 0, "fxsd", 3 * hz);
			dma_fence_signal(&s->fence);
			s->phase = 2;
		} else if (s->phase == -1) {
			break;
		}
	}
	kthread_exit();
}

/* ------------------------------------------------------------------ */
/* local copies of the statics in linux_fence.c (they are not exported) */

struct default_wait_cb {
	struct dma_fence_cb	base;
	struct task_struct	*task;
};

static void
fx_wait_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
{
	struct default_wait_cb *w =
	    container_of(cb, struct default_wait_cb, base);

	wake_up_process(w->task);
}

static bool
fx_signaled_any(struct dma_fence **fences, uint32_t count, uint32_t *idx)
{
	int i;

	for (i = 0; i < count; ++i) {
		if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
		    &fences[i]->flags)) {
			if (idx)
				*idx = i;
			return true;
		}
	}
	return false;
}

/* ------------------------------------------------------------------ */
/* FIXED wait_any: the schedule_timeout() idiom                        */

static long
fx_wait_any_fixed(struct dma_fence **fences, uint32_t count,
    bool intr, long timeout, uint32_t *idx)
{
	struct default_wait_cb *cb;
	long ret = timeout;
	int i;

	if (timeout == 0) {
		for (i = 0; i < count; i++) {
			if (dma_fence_is_signaled(fences[i])) {
				if (idx)
					*idx = i;
				return 1;
			}
		}
		return 0;
	}

	cb = kcalloc(count, sizeof(struct default_wait_cb), GFP_KERNEL);
	if (cb == NULL)
		return -ENOMEM;

	for (i = 0; i < count; i++) {
		cb[i].task = current;
		if (dma_fence_add_callback(fences[i], &cb[i].base,
		    fx_wait_cb)) {
			if (idx)
				*idx = i;
			goto cb_cleanup;
		}
	}

	if (timeout == MAX_SCHEDULE_TIMEOUT) {
		for (;;) {
			if (fx_signaled_any(fences, count, idx)) {
				ret = 1;
				break;
			}
			__set_current_state(intr ? TASK_INTERRUPTIBLE :
			    TASK_UNINTERRUPTIBLE);
			schedule_timeout(MAX_SCHEDULE_TIMEOUT);
			__set_current_state(TASK_RUNNING);
			if (intr && signal_pending(current)) {
				ret = -ERESTARTSYS;
				break;
			}
		}
	} else {
		unsigned long end = jiffies + timeout;
		for (ret = timeout; ret > 0;
		    ret = MAX(0L, (long)(end - jiffies))) {
			if (fx_signaled_any(fences, count, idx))
				break;
			__set_current_state(intr ? TASK_INTERRUPTIBLE :
			    TASK_UNINTERRUPTIBLE);
			schedule_timeout(ret);
			__set_current_state(TASK_RUNNING);
			if (intr && signal_pending(current)) {
				ret = -ERESTARTSYS;
				break;
			}
		}
	}

cb_cleanup:
	while (i-- > 0)
		dma_fence_remove_callback(fences[i], &cb[i].base);
	kfree(cb);
	return ret;
}

/* ------------------------------------------------------------------ */
/* TEST A: clean-timeout wrap (baseline vs fixed)                      */

static void
test_a(void)
{
	struct dma_fence f, *arr[1] = { &f };
	spinlock_t lock;
	long r;
	int i, neg = 0, zero = 0, pos = 0;
	int fneg = 0, fzero = 0, fpos = 0;

	lockinit(&lock, "fxal", 0, 0);

	for (i = 0; i < 400; i++) {
		dma_fence_init(&f, &fx_ops, &lock, 0, i);
		r = dma_fence_wait_any_timeout(arr, 1, false, 1, NULL);
		if (r < 0) neg++;
		else if (r == 0) zero++;
		else pos++;

		dma_fence_init(&f, &fx_ops, &lock, 0, i);
		r = fx_wait_any_fixed(arr, 1, false, 1, NULL);
		if (r < 0) fneg++;
		else if (r == 0) fzero++;
		else fpos++;
	}

	kprintf("[DF5017] TEST A (400 unsignaled waits, timeout=1 tick)\n");
	kprintf("[DF5017]   baseline wait_any: ret<0:%d  ret==0:%d  ret>0:%d"
	    "  (contract: every ret must be 0)\n", neg, zero, pos);
	kprintf("[DF5017]   fixed     wait_any: ret<0:%d  ret==0:%d  ret>0:%d\n",
	    fneg, fzero, fpos);
	kprintf("[DF5017]   VERDICT A: %s\n",
	    neg ? "BUG โ€” negative return on clean timeout" :
	    "no negative observed this run (see VERDICT.md)");
}

/* ------------------------------------------------------------------ */
/* TEST B: MAX_SCHEDULE_TIMEOUT, signal after 3s (baseline vs fixed)   */

static void
test_b(void)
{
	struct dma_fence *arr[1] = { &fx.fence };
	long r;
	int t0, t1, res;

	dma_fence_init(&fx.fence, &fx_ops, &fx.lock, 0, 1);
	fx.phase = 1;
	wakeup(&fx.phase);
	t0 = ticks;
	r = dma_fence_wait_any_timeout(arr, 1, false,
	    MAX_SCHEDULE_TIMEOUT, NULL);
	t1 = ticks;
	while (fx.phase != 2)
		tsleep(&fx.phase, 0, "fxbw", hz);
	kprintf("[DF5017] TEST B baseline: MAX_SCHEDULE_TIMEOUT intr=false,"
	    " signal@3s -> ret=%ld elapsed=%d ticks\n", r, t1 - t0);

	dma_fence_init(&fx.fence, &fx_ops, &fx.lock, 0, 2);
	fx.phase = 1;
	wakeup(&fx.phase);
	t0 = ticks;
	r = fx_wait_any_fixed(arr, 1, false, MAX_SCHEDULE_TIMEOUT, NULL);
	t1 = ticks;
	while (fx.phase != 2)
		tsleep(&fx.phase, 0, "fxbw", hz);
	res = (r > 0 && t1 - t0 >= 3 * hz - 10 && t1 - t0 <= 3 * hz + hz);
	kprintf("[DF5017] TEST B fixed    : MAX_SCHEDULE_TIMEOUT intr=false,"
	    " signal@3s -> ret=%ld elapsed=%d ticks (%s)\n", r, t1 - t0,
	    res ? "correct" : "UNEXPECTED");
}

/* ------------------------------------------------------------------ */
/* TEST C (lost-wakeup primitive) was run in the first guest session    */
/* (run.log: trial 4 slept the full hz despite an immediate wakeup).    */
/* It is retired from the automated flow: its waiter kthread lifecycle */
/* stalls the harness on exit, which is a harness artifact.            */

/* ------------------------------------------------------------------ */
/* TEST E: the truncated timo โ€” (int)MAX_SCHEDULE_TIMEOUT == -1        */

static void
test_e(void)
{
	int n = 0;
	int t0 = ticks;

	while (ticks - t0 < hz) {
		tsleep(&n, 0, "fxe", (int)MAX_SCHEDULE_TIMEOUT);
		n++;
	}
	kprintf("[DF5017] TEST E: tsleep timo=(int)MAX_SCHEDULE_TIMEOUT=%d"
	    " -> %d wakeups in 1s (infinite sleep would be 0 wakeups;"
	    " wait_any loop => tick-rate poll)\n",
	    (int)MAX_SCHEDULE_TIMEOUT, n);
}

/* ------------------------------------------------------------------ */

static int
df5017_modevent(module_t mod, int type, void *data)
{
	struct thread *sigtd;
	int error;

	switch (type) {
	case MOD_LOAD:
		kprintf("[DF5017] loaded; hz=%d ticks=%lu\n", hz,
		    (unsigned long)jiffies);
		lockinit(&fx.lock, "fxbl", 0, 0);
		fx.phase = 0;
		error = kthread_create(fx_signaler, &fx, &sigtd, "fxsig");
		if (error)
			return error;

		test_a();	/* wrap misreport, baseline vs fixed */
		test_b();	/* MAX_SCHEDULE_TIMEOUT, baseline vs fixed */
		test_e();	/* timo truncation poll */

		fx.phase = -1;
		wakeup(&fx.phase);
		tsleep(&fx.phase, 0, "fxbye", hz);	/* reap signaler */
		kprintf("[DF5017] all tests done\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t df5017_mod = { "df5017", df5017_modevent, NULL };
DECLARE_MODULE(df5017, df5017_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);