⬢ DragonFlyBSD Kernel Audit
DF-4046 / stgereplica.c
← back to finding ↓ download raw
/*
 * stgereplica.c - deterministic replica of if_stge.c's sc_tick_ch callout
 * protocol (stge(4) instance of the DF-3715(em) / DF-4025(re) AB-BA family).
 *
 * if_stge.c arms ONE callout with plain callout_init() -- the NON-MPSAFE
 * variant, dispatched by softclock under the MPLOCK (if_stge.c:587,
 * kern_timeout.c:575-590) -- whose handler takes the driver serializer by
 * hand and re-arms UNCONDITIONALLY while holding it:
 *
 *   stge_tick()   if_stge.c:1752-1778
 *                 :1758 lwkt_serialize_enter (BLOCKING acquire)
 *                 :1761 mii_tick / :1764 stge_stats_update /
 *                 :1772-1773 conditional stge_txeof
 *                 :1775 callout_reset(..., hz, stge_tick, sc)  <-- no
 *                      IFF_RUNNING gate at all (unlike re_tick_serialized)
 *                 :1777 lwkt_serialize_exit
 *
 * stge_stop() (if_stge.c:2098-2169) tears it down with
 * callout_stop(&sc->sc_tick_ch) at if_stge.c:2111 -- the FIRST action after
 * ASSERT_SERIALIZED -- while HOLDING that serializer.  Every stge_stop
 * caller holds it: stge_detach :820-825, stge_shutdown :1049-1051,
 * stge_suspend :1060-1063, stge_ioctl SIOCSIFFLAGS :1280, stge_init
 * :1900/:1907 (itself reachable from stge_ioctl SIOCSIFMTU :1265, the
 * watchdog :1238, and stge_intr's force_init :1438-1440).  IFF_RUNNING is
 * cleared only at the very END of stge_stop (:2166).
 *
 * DragonFly callout_stop() is synchronous (kern_timeout.c:1091-1093 ->
 * _callout_cancel_or_stop(..., sync=1); ssleep "costp" at :920): it waits
 * for an INPROG callback to finish.  A stge_tick dequeued by a softclock
 * thread that is parked in lwkt_serialize_enter therefore deadlocks
 * against the stopper (AB-BA): both threads wedge permanently, the
 * interface serializer is never released (all later ioctls/if_start on
 * stgeX block), and the affected softclock stops delivering ALL callouts
 * on that CPU.  stge_tick's unconditional re-arm (:1775) additionally
 * means nothing in the driver can prevent the parked callback from
 * re-arming once it eventually runs.
 *
 * Sysctls:
 *   kern.stgereplica.arm  =1  arm the stge_tick replica (short period for
 *                              a deterministic window; real driver uses hz)
 *   kern.stgereplica.stop =1  stge_stop replica: hold serializer, wait
 *                              until the handler is parked, callout_stop()
 *                              -> if the AB-BA analysis is right this NEVER
 *                              returns.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/serialize.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

static struct lwkt_serialize ser;	/* ifp->if_serializer replica */
static struct callout tick_c;		/* sc->sc_tick_ch replica */
static volatile int running;		/* IFF_RUNNING replica */
static volatile int tick_fired, tick_ran;

static void	tick_handler(void *);

static void
tick_handler(void *arg)			/* if_stge.c:1752-1778 shape */
{
	tick_fired++;				/* pre-acquire */
	lwkt_serialize_enter(&ser);		/* if_stge.c:1758 (BLOCKING) */
	tick_ran++;
	/* mii_tick / stats / conditional txeof elided */
	callout_reset(&tick_c, 1, tick_handler, arg); /* :1775, UNCONDITIONAL */
	lwkt_serialize_exit(&ser);		/* if_stge.c:1777 */
}

static int
arm_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return error;
	if (v == 1) {
		tick_fired = tick_ran = 0;
		running = 1;
		/* if_stge.c:2059 callout_reset(&sc->sc_tick_ch, hz, ...) */
		callout_reset(&tick_c, 1, tick_handler, NULL);
		kprintf("stgereplica: armed (stge_tick shape, 1-tick period; "
		    "stge uses hz)\n");
	}
	return 0;
}

static int
stop_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return error;
	if (v != 1)
		return 0;

	/*
	 * stge_stop() shape - if_stge.c:2106 ASSERT_SERIALIZED,
	 * :2111 callout_stop(), :2166 clear IFF_RUNNING (last), in that
	 * order, all while HOLDING the serializer.
	 */
	lwkt_serialize_enter(&ser);
	while (tick_ran == tick_fired)	/* wait until handler is parked */
		tsleep(&tick_ran, 0, "stgetkw", 2);
	kprintf("stgereplica: stge_tick parked on our serializer "
	    "(fired=%d ran=%d) - entering callout_stop()\n",
	    tick_fired, tick_ran);
	kprintf("stgereplica: IF THIS IS THE LAST stgereplica LINE, "
	    "AB-BA DEADLOCK IS PROVEN\n");
	callout_stop(&tick_c);		/* if_stge.c:2111 */
	running = 0;			/* if_stge.c:2166 */
	kprintf("stgereplica: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_exit(&ser);
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, stgereplica, CTLFLAG_RW, 0,
    "stge(4) callout replica");
SYSCTL_PROC(_kern_stgereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm stge_tick replica");
SYSCTL_PROC(_kern_stgereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "stge_stop replica vs stge_tick (AB-BA)");

static int
stgereplica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&tick_c);	/* if_stge.c:587: plain NON-MP init */
		running = 0;
		kprintf("stgereplica: loaded (hz=%d)\n", hz);
		return 0;
	case MOD_UNLOAD:
		callout_stop_async(&tick_c);
		return 0;
	}
	return 0;
}

static moduledata_t stgereplica_mod = {
	"stgereplica",
	stgereplica_load,
	NULL
};
DECLARE_MODULE(stgereplica, stgereplica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);