⬢ DragonFlyBSD Kernel Audit
DF-2579 / rig.c
← back to finding ↓ download raw
/*
 * DF-2579 rig -- ieee80211_tdma.c:284-289 silent uint16_t truncation of
 * iv_swbmiss_period (IEEE80211_TU_TO_TICKS(...) is uint64_t) can yield 0
 * -> callout_reset(..., 0, ieee80211_swbmiss) -> per-tick bmiss flood.
 *
 * Verbatim: the assignment + callout_reset lines from
 * ieee80211_tdma.c:284-289.  Numbers that truncate to 0 with THIS GUEST's
 * real hz (=100): bmissthreshold=5, bintval=250, slotcnt=8, slotlen=32768
 *   inner TU = 2*5*250*((8*32768)/1024) = 640000
 *   ticks   = 640000*1024*100/1000000 = 65536  -> (uint16_t)0
 *
 * sysctl kern.dfv2579.run:
 *   0 = arithmetic proof (verbatim expression) for the truncating params
 *   1 = control params (bintval=100 -> 25600 TU -> 2621 ticks, no trunc)
 *   2 = callout flood A/B: arm with computed 0 period vs sane period,
 *       count fires in 200 ms
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/callout.h>
#include <sys/interrupt.h>

#include <net/if_var.h>
#include <net/if_media.h>
#include <netproto/802_11/ieee80211_var.h>

static char df_status[4096];
static struct callout df_co;
static int df_fires;
static struct lock df_co_lock;

/* the truncation demo fields (same types as ieee80211vap) */
static uint16_t demo_iv_swbmiss_period;
static uint16_t demo_iv_swbmiss_count;

static void
df_swbmiss(void *arg)
{
	atomic_add_int(&df_fires, 1);
	demo_iv_swbmiss_count++;
	/* ieee80211_swbmiss re-arms with the same period (tdma keeps 0) */
	if (demo_iv_swbmiss_period == 0)
		callout_reset(&df_co, 0, df_swbmiss, arg);
	else
		callout_reset(&df_co, demo_iv_swbmiss_period, df_swbmiss, arg);
}

static void
df_run2579(int mode)
{
	char *p = df_status;
	int t = 0;
	int hz_real = hz;
	int bmissthreshold = 5;
	int bintval, slotcnt = 8, slotlen = 32768;
	uint64_t inner_tu, ticks64;
	uint16_t period16;

	if (mode == 1) {
		bintval = 100;			/* control: no truncation */
	} else {
		bintval = 250;			/* truncating params */
	}
	slotcnt = 8; slotlen = 32768;

	/* VERBATIM expression from ieee80211_tdma.c:284-286 */
	inner_tu = 2 * bmissthreshold * bintval *
	    ((slotcnt * slotlen) / 1024);
	ticks64 = IEEE80211_TU_TO_TICKS(inner_tu);
	period16 = ticks64;			/* uint16_t store */

	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2579[mode=%d]: hz(real)=%d bintval=%d slotcnt=%d slotlen=%d "
	    "bmissthreshold=%d\n", mode, hz_real, bintval, slotcnt, slotlen,
	    bmissthreshold);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2579: inner TU=%llu TU_TO_TICKS=%llu -> uint16 period=%u "
	    "(truncated from %llu)\n",
	    (unsigned long long)inner_tu, (unsigned long long)ticks64,
	    period16, (unsigned long long)ticks64);

	if (mode <= 1)
		return;

	/* mode 2: flood A/B with real callouts */
	df_fires = 0;
	demo_iv_swbmiss_period = period16;	/* 0 for the vuln params */
	callout_init(&df_co);
	callout_reset(&df_co, demo_iv_swbmiss_period, df_swbmiss, NULL);
	tsleep(&df_status, 0, "df2579a", hz_real / 5);	/* 200ms */
	int fires_zero = df_fires;
	callout_stop(&df_co);

	df_fires = 0;
	demo_iv_swbmiss_period = ticks64 ? 6553 : 1;	/* ~6.5s sane period */
	callout_reset(&df_co, demo_iv_swbmiss_period, df_swbmiss, NULL);
	tsleep(&df_status, 0, "df2579b", hz_real / 5);
	int fires_sane = df_fires;
	callout_stop(&df_co);

	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2579: fires in 200ms with period=0: %d ; with sane period "
	    "6553 ticks: %d\n", fires_zero, fires_sane);
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "DF2579: RESULT=%s\n", fires_zero > 10 * fires_sane + 10 ?
	    "period-0 callout FLOOD (beacon-miss storm: every tick, "
	    "taskqueue saturation, radio DoS)" :
	    "flood not observed this run");
}

static int
dfv2579_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[4096];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		switch (v) {
		case 0: df_run2579(0); break;
		case 1: df_run2579(1); break;
		case 2: df_run2579(2); break;
		default: return EINVAL;
		}
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv2579_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv2579_sysctl, "I", "dfv2579");
SYSCTL_PROC(_kern, OID_AUTO, dfv2579_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfv2579_sysctl, "A", "dfv2579 status");

static int
dfv2579_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lockinit(&df_co_lock, "df2579", 0, 0);
		kprintf("dfv2579: loaded (hz=%d)\n", hz);
		return 0;
	default:
		return 0;
	}
}
static moduledata_t dfv2579_mod = { "dfv2579", dfv2579_load, NULL };
DECLARE_MODULE(dfv2579, dfv2579_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);