DragonFlyBSD Kernel Audit
DF-0351 / hwmp_metric_overflow.c
← back to finding ↓ download raw
/*
 * DF-0351 — HWMP uint32 metric-accumulation overflow harness.
 *
 * This finding targets the in-kernel 802.11s HWMP routing code in
 * sys/netproto/802_11/wlan/ieee80211_hwmp.c.  It is a LOGIC / PROTOCOL
 * vulnerability (route poisoning / hijacking via integer overflow), NOT a
 * kernel memory-corruption primitive, so there is no slab/UAF escalation
 * chain to develop.  The audit guest also has no WiFi hardware and no
 * mesh-mode VAP, so the live in-kernel path cannot be driven here.  This
 * harness instead reproduces the EXACT arithmetic and route-accept logic of
 * the cited code paths, proving the overflow is real and attacker-controlled,
 * and proving the saturating-add fix closes it.
 *
 * Why attacker-controlled (no WiFi HW needed to see it):
 *   ieee80211_hwmp.c:458  preq->preq_metric = le32dec(iefrm_t);
 *   The metric field is read verbatim from the on-air HWMP frame with NO
 *   range/sanity check, so an attacker transmitting a PREQ sets it to any
 *   uint32_t, including values crafted to make `preq_metric + link_metric`
 *   wrap modulo 2^32.  PREP (:1453) and RANN (:1972) metrics are parsed the
 *   same way (analogous le32dec sites).
 *
 * Overflow sites (all plain uint32_t add, no saturation):
 *   :1089  metric = preq->preq_metric + mpm_metric(ni);          (PREQ recv)
 *   :1325  ppreq.preq_metric += mpm_metric(ni);                  (PREQ fwd)
 *   :1453  metric = prep->prep_metric + mpm_metric(ni);          (PREP recv)
 *   :1557  pprep.prep_metric += mpm_metric(ni);                  (PREP fwd)
 *   :1972  metric = rann->rann_metric + mpm_metric(ni);          (RANN recv)
 *
 * Build:  cc -O2 -Wall -o hwmp_metric_overflow hwmp_metric_overflow.c
 * Run:    ./hwmp_metric_overflow
 */
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

/* ---- replicated verbatim from ieee80211_hwmp.c:127-134 ---- */
typedef uint32_t ieee80211_hwmp_seq;
#define	HWMP_SEQ_GT(a, b)	((int32_t)((a)-(b)) > 0)
#define	HWMP_SEQ_EQ(a, b)	((a) == (b))

/* ---- route-table fields we mutate (rt_metric / hr_seq / nexthop set) ---- */
static uint32_t stored_rt_metric;       /* rtorig->rt_metric  (ieee80211_mesh.h:429) */
static ieee80211_hwmp_seq stored_hr_seq;/* hrorig->hr_seq                       */
static uint32_t attacker_nexthop_set;   /* route->rt_nexthop = attacker ? 1:0   */

/* ieee80211_hwmp.c:1089 EXACTLY — plain uint32 add, wraps modulo 2^32 */
static uint32_t hwmp_metric_add_vuln(uint32_t a, uint32_t b) { return a + b; }

/* proposed fix: saturating add (clamp at UINT32_MAX), HWMP-spec-bounded */
static uint32_t hwmp_metric_add_fixed(uint32_t a, uint32_t b) {
	uint64_t s = (uint64_t)a + (uint64_t)b;
	return s > UINT32_MAX ? UINT32_MAX : (uint32_t)s;
}

/*
 * Replicates hwmp_recv_preq() accept logic, ieee80211_hwmp.c:1089-1102.
 * On a fresh sequence number the first clause is TRUE and the route is
 * updated UNCONDITIONALLY (metric comparison skipped); on an equal-seq PREQ
 * the wrapped metric must still beat the stored metric to take effect.
 */
static int recv_preq(uint32_t (*add)(uint32_t, uint32_t),
		     ieee80211_hwmp_seq preq_origseq, uint32_t preq_metric,
		     uint32_t link_metric, const char *tag) {
	uint32_t metric = add(preq_metric, link_metric);          /* :1089 */
	int poisoned = 0;
	if (HWMP_SEQ_GT(preq_origseq, stored_hr_seq) ||
	    (HWMP_SEQ_EQ(preq_origseq, stored_hr_seq) &&
	     metric < stored_rt_metric)) {                        /* :1090-1092 */
		stored_hr_seq     = preq_origseq;                 /* :1093 */
		stored_rt_metric  = metric;                       /* :1095 */
		attacker_nexthop_set = 1;                         /* :1094/:1102 */
		poisoned = 1;
	}
	printf("  [%-14s] preq_metric=0x%08" PRIx32 " link=%-6u "
	       "=> accumulated=%-11u (0x%08" PRIx32 ")  %s\n",
	       tag, preq_metric, link_metric, metric, metric,
	       poisoned ? "*** ROUTE POISONED: attacker nexthop installed ***"
	                : "rejected (legit route preserved)");
	return poisoned;
}

int main(void) {
	const uint32_t LINK = 50000;          /* ~realistic 1-hop airtime metric */
	const uint32_t LEGIT_RT_METRIC = 5000000; /* 5 honest hops * 1e6 each    */

	printf("DF-0351 harness: HWMP uint32 metric-accumulation overflow\n");
	printf("(replicates ieee80211_hwmp.c arithmetic; no WiFi HW needed)\n\n");

	printf("== Baseline: honest route to originator, rt_metric=%u, hr_seq=100 ==\n\n",
	       LEGIT_RT_METRIC);

	/* ---- (1) VULNERABLE: fresh sequence number bypasses metric compare ---- */
	stored_rt_metric = LEGIT_RT_METRIC; stored_hr_seq = 100; attacker_nexthop_set = 0;
	printf("-- VULNERABLE kernel, FRESH seq (HWMP_SEQ_GT true -> update forced) --\n");
	recv_preq(hwmp_metric_add_vuln, /*origseq=*/101,
	          /*preq_metric=*/0xFFFFFFFF, LINK, "fresh-seq");
	printf("  -> rt_metric=%u (was %u); attacker_nexthop=%u\n\n",
	       stored_rt_metric, LEGIT_RT_METRIC, attacker_nexthop_set);

	/* ---- (2) VULNERABLE: equal seq, wrapped metric beats stored ---- */
	stored_rt_metric = LEGIT_RT_METRIC; stored_hr_seq = 100; attacker_nexthop_set = 0;
	printf("-- VULNERABLE kernel, EQUAL seq (wrapped metric < stored metric) --\n");
	recv_preq(hwmp_metric_add_vuln, /*origseq=*/100,
	          /*preq_metric=*/0xFFFFFFFF, LINK, "equal-seq");
	printf("  -> rt_metric=%u (legit was %u); attacker_nexthop=%u\n\n",
	       stored_rt_metric, LEGIT_RT_METRIC, attacker_nexthop_set);

	/* ---- (3) VULNERABLE: PREQ forwarding accumulation, ieee80211_hwmp.c:1325 ---- */
	printf("-- VULNERABLE kernel, PREQ forward accumulation (:1325) --\n");
	{
		/* Attacker seeds ppreq.preq_metric near UINT32_MAX.  Each honest
		 * forwarder does ppreq.preq_metric += mpm_metric(ni) and re-broadcasts;
		 * the very first forward hop wraps the metric to a tiny value, which is
		 * then propagated to (and poisons) every downstream mesh node. */
		uint32_t fwd = 0xFFFFFFFE; uint32_t l1 = 50000, l2 = 80000;
		printf("  attacker seeds fwd_metric=0x%08" PRIx32 "\n", fwd);
		fwd += l1;                                   /* :1325  wraps */
		printf("  hop1: 0xFFFFFFFE + %u = %u (0x%08" PRIx32 ")  <-- WRAPS\n",
		       l1, fwd, fwd);
		fwd += l2;                                   /* re-broadcast onward */
		printf("  hop2: prev + %u = %u  -> propagated to all downstream nodes\n\n",
		       l2, fwd);
	}

	/* ---- (4) FIXED: saturating add, no wrap ---- */
	stored_rt_metric = LEGIT_RT_METRIC; stored_hr_seq = 100; attacker_nexthop_set = 0;
	printf("-- FIXED kernel (saturating add -> metric clamps to UINT32_MAX) --\n");
	recv_preq(hwmp_metric_add_fixed, /*origseq=*/100,
	          /*preq_metric=*/0xFFFFFFFF, LINK, "equal-seq");
	printf("  -> rt_metric=%u (legit PRESERVED); attacker_nexthop=%u\n\n",
	       stored_rt_metric, attacker_nexthop_set);

	printf("VERDICT: vulnerable kernel accepts wrapped near-0 metric and installs the\n");
	printf("         attacker as the nexthop (route poisoning / hijack / MITM / blackhole).\n");
	printf("         Fixed kernel saturates the sum at UINT32_MAX, so the wrapped tiny\n");
	printf("         value never appears and the legitimate route is preserved.\n");
	return 0;
}