DragonFlyBSD Kernel Audit
DF-2941 / sysref_probe.c
← back to finding ↓ download raw
/*
 * sysref_probe.c -- kern_sysref.c layer probes for DF-2941 / DF-2942.
 *
 * DF-2942 (resurrection): sysref_activate()'s KASSERT at
 * kern_sysref.c:280 accepts count == -0x40000000 -- the exact state of an
 * object that just entered termination-in-progress (the 1 -> -0x40000000
 * transition at kern_sysref.c:323).  Trigger A drives a cdev-style object
 * into termination, then calls sysref_activate() *inside* the terminate
 * callback (simulating a racing misuser).  The layer KASSERT passes, the
 * object is resurrected to refcnt +1, and the protocol then runs the
 * terminate callback a SECOND time (double termination).  For any real
 * class (e.g. devfs_cdev_terminate) the second terminate re-executes the
 * class teardown (double devfs_release_ops, double unlock) -- here we only
 * count and log it, deterministically, without guest corruption.
 *
 * DF-2941 (put-side floor): _sysref_put()'s branch
 * `else if (count > -0x40000000)` (kern_sysref.c:329) also accepts
 * count == 0, so a double-release walks a putaway object 0 -> -1 with no
 * structural guard; the ONLY barrier is the debug-only KKASSERT at
 * kern_sysref.c:303 reading SRF_PUTAWAY non-atomically w.r.t. the
 * refcnt cmpset (:346) -> flags RMW (:348) pair.  Trigger B:
 *   B1: normal alloc/activate/get/put/put cycle (protocol baseline);
 *   B2: fault injection emulating a put that enters _sysref_put between
 *       :346 and :348 (SRF_PUTAWAY not yet visible): the double-release
 *       is ACCEPTED, refcnt walks 0 -> -1, logged live;
 *   B3: a real post-putaway double-put: the KKASSERT fires -> guest
 *       panic (proving the guard is debug-kernel-only; production builds
 *       compile it out and take the B2 path silently).
 *
 * Build: see build.sh.  Run: kldload ./sysref_probe.ko (root).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/sysctl.h>
#include <sys/sysref.h>
#include <sys/sysref2.h>

struct probe_obj {
	struct sysref	sr;
	uint64_t	magic;
	char		pad[64];
};

MALLOC_DEFINE(M_SYSPROBE, "sysprobe", "sysref layer probe objects");

static struct lock	probe_lk;
static int		term_count;
static int		resurrect_armed;
static struct probe_obj *pa;
static struct probe_obj *pb;

static void	probe_terminate(void *data);
static void	probe_lock(void *data);
static void	probe_unlock(void *data);

static struct sysref_class probe_class = {
	.name =		"sysprobe",
	.mtype =	M_SYSPROBE,
	.proto =	0,
	.offset =	offsetof(struct probe_obj, sr),	/* 0 */
	.objsize =	sizeof(struct probe_obj),
	.nom_cache =	4,
	.flags =	0,
	.oc =		NULL,
	.ctor =		NULL,
	.dtor =		NULL,
	.ops = {
		.terminate =	probe_terminate,
		.lock =		probe_lock,
		.unlock =	probe_unlock,
	},
};

static void
probe_lock(void *data)
{
	lockmgr(&probe_lk, LK_EXCLUSIVE);
}

static void
probe_unlock(void *data)
{
	lockmgr(&probe_lk, LK_RELEASE);
}

/*
 * Mirrors devfs_cdev_terminate() protocol shape: the callback is invoked
 * with ops.lock held (acquired in _sysref_put's count==1 branch) and is
 * responsible for unlocking, then dropping the terminal reference.
 */
static void
probe_terminate(void *data)
{
	struct probe_obj *o = data;
	int n;

	n = ++term_count;
	kprintf("sysprobe:   terminate#%d obj=%p entry refcnt=%d\n",
	    n, o, o->sr.refcnt);

	if (o == pa && resurrect_armed) {
		/* DF-2942: resurrect the terminating object (ONE-SHOT:
		 * run 1 showed that re-activating on every terminate loops
		 * unbounded -- 292+ re-terminations until kernel stack
		 * exhaustion / double fault.  See run.1.unbounded.log). */
		resurrect_armed = 0;
		kprintf("sysprobe:   DF-2942: sysref_activate() on "
		    "TERMINATING obj, before=%d\n", o->sr.refcnt);
		sysref_activate(&o->sr);
		kprintf("sysprobe:   DF-2942: after activate refcnt=%d "
		    "(RESURRECTED; kern_sysref.c:280 KASSERT passed, "
		    "no diagnostic)\n", o->sr.refcnt);
	}

	probe_unlock(data);
	sysref_put(&o->sr);			/* terminal ref */
	kprintf("sysprobe:   terminate#%d exit refcnt=%d\n", n, o->sr.refcnt);
}

static void
trigger_A(void)
{
	kprintf("sysprobe: === DF-2942 trigger A: activate/termination "
	    "negative-space ambiguity ===\n");

	resurrect_armed = 1;
	pa = sysref_alloc(&probe_class);
	kprintf("sysprobe: A: alloc       refcnt=%d magic=%llx\n",
	    pa->sr.refcnt, (unsigned long long)pa->magic);
	sysref_activate(&pa->sr);
	kprintf("sysprobe: A: activate    refcnt=%d\n", pa->sr.refcnt);
	sysref_get(&pa->sr);
	sysref_put(&pa->sr);
	kprintf("sysprobe: A: get/put     refcnt=%d\n", pa->sr.refcnt);
	kprintf("sysprobe: A: final put -> termination-in-progress...\n");
	sysref_put(&pa->sr);
	kprintf("sysprobe: A: result      term_count=%d refcnt=%d\n",
	    term_count, pa->sr.refcnt);
	if (term_count != 2)
		kprintf("sysprobe: A: UNEXPECTED term_count\n");
	else
		kprintf("sysprobe: A: CONFIRMED DOUBLE TERMINATION "
		    "(terminate callback ran twice for one object)\n");
	pa = NULL;
}

static void
trigger_B(void)
{
	kprintf("sysprobe: === DF-2941 trigger B: put-side count==0 "
	    "acceptance ===\n");

	term_count = 0;
	pb = sysref_alloc(&probe_class);
	sysref_activate(&pb->sr);
	sysref_get(&pb->sr);
	sysref_put(&pb->sr);
	sysref_put(&pb->sr);
	kprintf("sysprobe: B1: normal cycle  refcnt=%d flags=%04x "
	    "term_count=%d (putaway)\n",
	    pb->sr.refcnt, pb->sr.flags, term_count);

	/*
	 * B2: fault injection.  A put racing the final release between the
	 * refcnt cmpset (kern_sysref.c:346) and the SRF_PUTAWAY flags RMW
	 * (:348) sees exactly this state: count==0, PUTAWAY not visible.
	 * Emulate it, then issue the double-release.
	 */
	pb->sr.flags &= ~SRF_PUTAWAY;
	kprintf("sysprobe: B2: injected pre-:348 visibility; issuing "
	    "double-put\n");
	sysref_put(&pb->sr);
	kprintf("sysprobe: B2: layer ACCEPTED double-release: refcnt=%d "
	    "(walked 0 -> -1; `count > -0x40000000` branch, no floor guard)\n",
	    pb->sr.refcnt);

	/* repair so the objcache state stays consistent for B3 */
	pb->sr.refcnt = 0;
	pb->sr.flags |= SRF_PUTAWAY;
	kprintf("sysprobe: B2: repaired    refcnt=%d flags=%04x\n",
	    pb->sr.refcnt, pb->sr.flags);
}

static int
sysref_probe_modevent(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lockinit(&probe_lk, "sysprobe", 0, 0);
		trigger_A();
		trigger_B();
		kprintf("sysprobe: triggers A+B complete; now DF-2941 B3: "
		    "real post-putaway double-put (expect KKASSERT panic "
		    "at kern_sysref.c:303 on this INVARIANTS kernel)\n");
		sysref_put(&pb->sr);
		kprintf("sysprobe: B3: UNREACHED (accepted silently)\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t sysref_probe_mod = {
	"sysref_probe",
	sysref_probe_modevent,
	NULL
};

DECLARE_MODULE(sysref_probe, sysref_probe_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(sysref_probe, 1);