⬢ DragonFlyBSD Kernel Audit
DF-5605 / lockowned_demo.c
← back to finding ↓ download raw
/*
 * DF-5605 KLD harness — demonstrates the DragonFly-specific lockowned()
 * semantics divergence relied on by ua_mixer_set()/ua_mixer_setrecsrc()
 * (sys/bus/u4b/audio/uaudio_pcm.c:139 / :159).
 *
 * Production kernel APIs only (lockmgr/lockowned/lockstatus/kthread_create);
 * no debug interfaces.
 *
 * What it shows, phase by phase (all kprintf'd with DF5605 markers):
 *   P1 lock free                          -> lockowned=0  (dance acquires: correct)
 *   P2 lock held exclusive by curthread   -> lockowned=1  (dance skips reacquire: correct)
 *   P3 lock held SHARED by ANOTHER thread -> lockowned=1  <-- WRONG for the
 *      ua_mixer_set() decision shape: curthread holds nothing, yet the dance
 *      concludes "already owned" and runs uaudio_mixer_set() with zero
 *      exclusion while the foreign thread is inside a shared critical
 *      section.  The precise check the sound(4) core itself uses
 *      (mixer.c:273 lockstatus(m->lock, curthread) == LK_EXCLUSIVE) returns
 *      LK_SHARED here and would (block and) acquire.
 *   P4 foreign holder released            -> lockowned=0  (back to correct)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/kthread.h>
#include <sys/thread.h>

static struct lock	 dance_lock;	/* created like snd_mtxcreate() */
static struct thread	*sh_tp;
static volatile int	 sh_state;	/* 0=not started 1=shared-held 2=released */

static void
shared_holder(void *arg __unused)
{
	lockmgr(&dance_lock, LK_SHARED);
	sh_state = 1;
	tsleep(&sh_state, 0, "df5605sh", 3 * hz);	/* hold ~3s, or until woken */
	lockmgr(&dance_lock, LK_RELEASE);
	sh_state = 2;
	wakeup(&sh_state);
	kthread_exit();
}

/*
 * The exact decision shape of ua_mixer_set(), uaudio_pcm.c:136-148.
 * Returns the do_unlock value the real code would compute.
 */
static int
ua_mixer_set_dance(void)
{
	struct lock	*lock = &dance_lock;
	uint8_t		 do_unlock;

	if (lockowned(lock)) {
		do_unlock = 0;			/* "we hold it" — skip acquire */
	} else {
		do_unlock = 1;
		lockmgr(lock, LK_EXCLUSIVE);
	}
	/* uaudio_mixer_set(sc, ...) would run here */
	if (do_unlock)
		lockmgr(lock, LK_RELEASE);
	return (do_unlock);
}

static void
snap(const char *phase)
{
	int	 owned = lockowned(&dance_lock);
	int	 st = lockstatus(&dance_lock, curthread);
	int	 do_unlock = ua_mixer_set_dance();

	kprintf("DF5605 %s: lockowned=%d lockstatus=%d "
	    "(LK_SHARED=1 LK_EXCLUSIVE=2 LK_EXCLOTHER=8) "
	    "-> ua_mixer_set dance do_unlock=%d %s\n",
	    phase, owned, st, do_unlock,
	    do_unlock == 0 ? "(runs uaudio_mixer_set with NO lock held)"
			   : "(acquired exclusive properly)");
}

static int
df5605_modevent(module_t mod, int type, void *arg)
{
	int		i;

	switch (type) {
	case MOD_LOAD:
		lockinit(&dance_lock, "df5605mix", 0, LK_CANRECURSE);
		sh_state = 0;

		snap("P1-free");

		lockmgr(&dance_lock, LK_EXCLUSIVE);
		snap("P2-self-exclusive");	/* reentrant call path */
		lockmgr(&dance_lock, LK_RELEASE);

		/* foreign shared holder, exactly the case lockowned() guesses
		 * wrong about: returns (count & LKC_SMASK) != 0 without
		 * checking WHO holds it (kern_lock.c:1365-1378). */
		if (kthread_create(shared_holder, NULL, &sh_tp,
		    "df5605_shold") != 0) {
			kprintf("DF5605: kthread_create failed\n");
			lockuninit(&dance_lock);
			return (ENOMEM);
		}
		for (i = 0; i < 10 && sh_state == 0; i++)
			tsleep(&sh_state, 0, "df5605w", hz);
		if (sh_state != 1) {
			kprintf("DF5605: shared holder never engaged\n");
			break;
		}
		snap("P3-foreign-SHARED");
		wakeup(&sh_state);
		for (i = 0; i < 10 && sh_state != 2; i++)
			tsleep(&sh_state, 0, "df5605w", hz);
		snap("P4-released");

		kprintf("DF5605 DONE: divergence demonstrated=%s\n",
		    "see P3 line above");
		break;
	case MOD_UNLOAD:
		/* threads finished; drain */
		for (i = 0; i < 10 && sh_state == 1; i++) {
			wakeup(&sh_state);
			tsleep(&sh_state, 0, "df5605w", hz);
		}
		lockuninit(&dance_lock);
		kprintf("DF5605 unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t df5605_mod = { "df5605_demo", df5605_modevent, NULL };
DECLARE_MODULE(df5605_demo, df5605_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df5605_demo, 1);