⬢ DragonFlyBSD Kernel Audit
DF-3455 / df3455.c
← back to finding ↓ download raw
/*
 * DF-3455 harness -- NOL/CAC timeout arithmetic is signed-overflow and
 * tick-wrap unsafe (ieee80211_dfs.c).
 *
 * VERBATIM:
 *	:483  static int ieee80211_nol_timeout = 30*60;   (sysctl RW!)
 *	:486  #define NOL_TIMEOUT  msecs_to_ticks(ieee80211_nol_timeout*1000)
 *	(ieee80211_dragonfly.h:130)
 *	      #define msecs_to_ticks(ms) (((ms)*hz)/1000)     (hz=100)
 *	:244-247 (dfs_timeout):
 *	      now = oldest = ticks;
 *	      if (ieee80211_time_after_eq(now, dfs->nol_event[i]+NOL_TIMEOUT))
 *	          -> channel released from NOL
 *	:268  callout_schedule_dfly(&dfs->nol_timer, oldest + NOL_TIMEOUT - now, ...)
 *
 * (a) sysctl write of nol_timeout >= 2147484 overflows
 *     nol_timeout*1000 (signed int UB; wraps negative) -> NOL_TIMEOUT
 *     negative -> radar'd channel released IMMEDIATELY.
 * (b) tick wrap: ticks is int; after ~248 days, nol_event+NOL_TIMEOUT
 *     overflows; the addition happens BEFORE the wrap-aware compare,
 *     so a channel whose nol_event is close to INT_MAX is released
 *     immediately as ticks wraps.
 */
#include <stdio.h>
#include <limits.h>

static int hz = 100;
#define	msecs_to_ticks(ms)	(((ms)*hz)/1000)
#define	NOL_TIMEOUT		msecs_to_ticks(ieee80211_nol_timeout*1000)

static int ieee80211_nol_timeout = 30*60;	/* sysctl RW */

/* verbatim: wrap-aware compare from sys/systimer.h family */
#define	time_after_eq(a, b)	((int)((a) - (b)) >= 0)

static int
dfs_timeout_release(int now, int nol_event)
{
	if (time_after_eq(now, nol_event + NOL_TIMEOUT))
		return 1;		/* released from NOL */
	return 0;
}

int
main(void)
{
	int now, ev, released_early = 0;

	printf("DF-3455 (DFS NOL/CAC arithmetic, ieee80211_dfs.c:486,244,268)\n");
	printf("[a] default sysctl: nol_timeout=%d -> NOL_TIMEOUT=%d ticks\n",
	    ieee80211_nol_timeout, (int)NOL_TIMEOUT);
	now = 1000000; ev = now;			/* radar just now */
	printf("    radar at t=%d: released now? %s (expect no)\n",
	    ev, dfs_timeout_release(now, ev) ? "YES" : "no");

	printf("[b] root sets sysctl net.wlan.nol_timeout=2147484\n");
	ieee80211_nol_timeout = 2147484;
	printf("    nol_timeout*1000 = %d (signed int overflow -> UB wrap)\n",
	    (int)(2147484 * 1000));
	{
		int ms = ieee80211_nol_timeout * 1000;	/* wrapped */
		int ticks_ = (ms * hz) / 1000;		/* wrapped again */
		printf("    runtime NOL_TIMEOUT = msecs_to_ticks(%d) = %d"
		    " ticks (configured value promises 180000 ticks = 30min)\n",
		    ms, ticks_);
		ev = 1000000;			/* radar detected */
		now = ev + ticks_;		/* nol_timer fires here */
		int rel = dfs_timeout_release(now, ev);
		printf("    released after %d ticks instead of 180000: %s\n",
		    ticks_, rel ? "EARLY RELEASE" : "no");
		released_early |= (rel && ticks_ < 180000);
	}

	printf("[c] tick wrap: radar just before the wrap, timer after\n");
	ieee80211_nol_timeout = 30*60;			/* NOL_TIMEOUT=180000 */
	ev = INT_MAX - 1000;			/* radar at 2147482647 */
	now = 500;				/* ticks wrapped to 500 */
	printf("    nol_event=%d, sum=%d (overflowed negative)\n",
	    ev, (int)(ev + NOL_TIMEOUT));
	int rel2 = dfs_timeout_release(now, ev);
	printf("    released ~%d ticks after radar (should be 180000)? %s\n",
	    now + 1000, rel2 ? "YES -- EARLY RELEASE" : "no");
	released_early |= rel2;

	printf("RESULT: %s\n", released_early ?
	    "REPRODUCED -- radar'd channel can be released from the NOL"
	    " immediately (root sysctl int-overflow and/or tick-wrap; both"
	    " pre-addition overflows sit before the wrap-aware compare)" :
	    "not reproduced");
	return (released_early ? 0 : 1);
}