⬢ DragonFlyBSD Kernel Audit
DF-5138 / df5138_wwproof.c
← back to finding ↓ download raw
/*
 * DF-5138 runtime proof: DragonFly's ww_mutex never returns -EALREADY
 * for a same-ticket relock, so ttm_eu_reserve_buffers()'s duplicate-BO
 * separation branch (ttm_execbuf_util.c:119-124) is dead code and a
 * duplicate BO in a CS list is silently recursively double-locked.
 *
 * This module performs the exact operation sequence ttm_eu_reserve_buffers()
 * performs when the SAME buffer object appears twice in the validation list:
 *
 *   ww_acquire_init(&ticket, &reservation_ww_class)
 *   __ttm_bo_reserve(bo, ...) -> ww_mutex_lock(&bo->resv->lock, ticket)   # 1st
 *   __ttm_bo_reserve(bo, ...) -> ww_mutex_lock(&bo->resv->lock, ticket)   # 2nd, same bo
 *
 * Linux contract: 2nd call returns -EALREADY, ttm_eu moves the entry to the
 *                 dups list, the resv stays locked exactly once.
 * DragonFly fact: 2nd call returns 0 (LK_CANRECURSE silent recursion,
 *                 linux_wwmutex.c:62 + kern_lock.c:125-143), both entries
 *                 stay in the list, and ww_mutex_unlock() decrements
 *                 ticket->acquired only once (first unlock NULLs ww->ctx,
 *                 second unlock skips the decrement) -> accounting desync.
 *
 * It then performs the two unlocks ttm_eu_fence_buffer_objects() would do
 * and reports ticket->acquired (Linux semantics: 0; DFly: 1) and whether
 * the lock is still held (must be: no).
 *
 * Needs drm.ko loaded (ww_mutex implementation lives in drm.ko on DFly):
 *   kldload drm
 * Build:  make SYSDIR=/usr/src/sys
 * Run:    kldload ./df5138_wwproof.ko ; dmesg | tail ; kldunload df5138_wwproof
 */

#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/errno.h>

#include <linux/ww_mutex.h>

/* Own ww class: keeps the module self-contained; ww_mutex_init() uses it
 * only for the wmesg name. The ww_mutex_lock()/ww_mutex_unlock() code under
 * test is the drm.ko implementation (sys/dev/drm/linux_wwmutex.c). */
static DEFINE_WW_CLASS(df5138_ww_class);

static struct ww_mutex ww;
static struct ww_acquire_ctx ctx;

static int
df5138_handler(module_t mod, int what, void *arg)
{
	int r_first, r_second, result;

	switch (what) {
	case MOD_LOAD:
		ww_mutex_init(&ww, &df5138_ww_class);
		ww_acquire_init(&ctx, &df5138_ww_class);

		/* ttm_execbuf_util.c:113 for the 1st occurrence of the BO */
		r_first = ww_mutex_lock(&ww, &ctx);

		/* ttm_execbuf_util.c:113 for the 2nd occurrence of the SAME BO.
		 * On Linux this is -EALREADY and lines 119-124 move the entry
		 * to the dups list. On DragonFly it recurses silently. */
		r_second = ww_mutex_lock(&ww, &ctx);

		kprintf("DF5138-PROOF: first_lock=%d second_lock=%d "
			"(EALREADY would be -%d)\n",
			r_first, r_second, EALREADY);
		kprintf("DF5138-PROOF: ticket->acquired after both locks = %d "
			"(Linux dups path never gets here: 2nd lock = -EALREADY,"
			" entry moved aside, value stays 1)\n",
			ctx.acquired);

		/* ttm_eu_fence_buffer_objects() unlocks each entry that is
		 * still in the list once. With a working -EALREADY the dup
		 * entry was moved to the dups list and is NOT unlocked again;
		 * with the current DFly recursion both entries stay in the
		 * list and both get unlocked. Mirror exactly that. */
		ww_mutex_unlock(&ww);
		if (r_second == 0)
			ww_mutex_unlock(&ww);	/* dup still in list (unfixed) */

		kprintf("DF5138-PROOF: after unlocks ticket->acquired = %d"
			" (correct value: 0; 1 proves the desync)\n",
			ctx.acquired);
		result = (int)ww_mutex_is_locked(&ww);
		kprintf("DF5138-PROOF: resv still locked? %d "
			"(0 = correctly released, lockmgr recursion is "
			"count-balanced)\n", result);

		ww_acquire_fini(&ctx);
		ww_mutex_destroy(&ww);

		if (r_first == 0 && r_second == 0 && ctx.acquired == 1 &&
		    result == 0) {
			kprintf("DF5138-PROOF: RESULT=REPRODUCED "
				"(no -EALREADY; silent recursive double-lock;"
				" ticket accounting desync)\n");
		} else if (r_first == 0 && r_second == -EALREADY &&
			   ctx.acquired == 0 && result == 0) {
			kprintf("DF5138-PROOF: RESULT=FIXED "
				"(-EALREADY returned; dup separated; "
				"accounting correct)\n");
		} else {
			kprintf("DF5138-PROOF: RESULT=NOT-AS-PREDICTED "
				"(first=%d second=%d acquired=%d locked=%d)\n",
				r_first, r_second, ctx.acquired, result);
		}
		break;
	case MOD_UNLOAD:
		kprintf("DF5138-PROOF: unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

DEV_MODULE(df5138_wwproof, df5138_handler, NULL);