DragonFlyBSD Kernel Audit
DF-0350 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/netproto/802_11/ieee80211_mesh.h b/sys/netproto/802_11/ieee80211_mesh.h
--- a/sys/netproto/802_11/ieee80211_mesh.h
+++ b/sys/netproto/802_11/ieee80211_mesh.h
@@ -506,6 +506,24 @@
 #define	IEEE80211_MESH_SEQ_LEQ(a, b)	((int32_t)((a)-(b)) <= 0)
 #define	IEEE80211_MESH_SEQ_GEQ(a, b)	((int32_t)((a)-(b)) >= 0)
 
+/*
+ * Bounds on the per-vap mesh routing table, to prevent an unauthenticated
+ * remote attacker from exhausting kernel memory by flooding HWMP PREQ/PREP/
+ * RANN frames with distinct spoofed originator/target addresses. Each entry is
+ * ~150 B + protocol private data; without a cap the table grows without bound
+ * (2^48 distinct MACs available). 4096 is far above any legitimate mesh BSS
+ * peer count while bounding worst-case per-vap memory to a few hundred KB.
+ */
+#define	IEEE80211_MESH_RT_MAX		4096	/* max routes per mesh vap */
+
+/*
+ * Upper bound on a route's lifetime (msec). HWMP PREQ/PREP carry a uint32
+ * lifetime field supplied by the (unauthenticated) peer; the default path
+ * lifetime is ~5 s (net.wlan.hwmp.pathlifetime). Clamp the attacker value so
+ * a single frame cannot pin an entry for ~49 days (0xFFFFFFFF msec).
+ */
+#define	IEEE80211_MESH_RT_LIFETIME_MAX_MS	(60 * 1000)	/* 60 s */
+
 struct ieee80211_mesh_state {
 	int				ms_idlen;
 	uint8_t				ms_id[IEEE80211_MESHID_LEN];
@@ -523,6 +541,7 @@
 	ieee80211_mesh_seq		ms_gateseq;
 	TAILQ_HEAD(, ieee80211_mesh_gate_route) ms_known_gates;
 	TAILQ_HEAD(, ieee80211_mesh_route)  ms_routes;
+	uint32_t			ms_rtcount;	/* # entries in ms_routes */
 	struct ieee80211_mesh_proto_metric *ms_pmetric;
 	struct ieee80211_mesh_proto_path   *ms_ppath;
 };
diff --git a/sys/netproto/802_11/wlan/ieee80211_mesh.c b/sys/netproto/802_11/wlan/ieee80211_mesh.c
--- a/sys/netproto/802_11/wlan/ieee80211_mesh.c
+++ b/sys/netproto/802_11/wlan/ieee80211_mesh.c
@@ -203,6 +203,16 @@
 
 	MESH_RT_LOCK_ASSERT(ms);
 
+	/*
+	 * Cap the per-vap route table to bound memory against HWMP PREQ/PREP/
+	 * RANN floods using spoofed addresses. Caller treats NULL as
+	 * "table full" (is_mesh_rtaddfailed++ and the frame is dropped).
+	 */
+	if (ms->ms_rtcount >= IEEE80211_MESH_RT_MAX) {
+		vap->iv_stats.is_mesh_rtaddfailed++;
+		return NULL;
+	}
+
 #if defined(__DragonFly__)
 	rt = kmalloc(ALIGN(sizeof(struct ieee80211_mesh_route)) +
 	    ms->ms_ppath->mpp_privlen, M_80211_MESH_RT, M_INTWAIT | M_ZERO);
@@ -223,6 +233,7 @@
 #endif
 		rt->rt_updtime = ticks;	/* create time */
 		TAILQ_INSERT_TAIL(&ms->ms_routes, rt, rt_next);
+		ms->ms_rtcount++;
 	}
 	return rt;
 }
@@ -271,6 +282,17 @@
 
 	KASSERT(rt != NULL, ("route is NULL"));
 
+	/*
+	 * Clamp the supplied lifetime. HWMP PREQ/PREP frames carry a uint32
+	 * lifetime (preq_lifetime/prep_lifetime) taken directly from the
+	 * (unauthenticated) peer; without a bound a single frame can pin a
+	 * route entry for ~49 days (0xFFFFFFFF msec). Treat as unsigned so the
+	 * implicit uint32->int conversion in the caller does not turn the max
+	 * into -1 and evade the clamp.
+	 */
+	if ((uint32_t)new_lifetime > IEEE80211_MESH_RT_LIFETIME_MAX_MS)
+		new_lifetime = (int)IEEE80211_MESH_RT_LIFETIME_MAX_MS;
+
 	now = ticks;
 	MESH_RT_ENTRY_LOCK(rt);
 
@@ -355,6 +377,8 @@
 mesh_rt_del(struct ieee80211_mesh_state *ms, struct ieee80211_mesh_route *rt)
 {
 	TAILQ_REMOVE(&ms->ms_routes, rt, rt_next);
+	KASSERT(ms->ms_rtcount > 0, ("mesh route count underflow"));
+	ms->ms_rtcount--;
 	/*
 	 * Grab the lock before destroying it, to be sure no one else
 	 * is holding the route.
@@ -684,6 +708,7 @@
 	ms->ms_ttl = IEEE80211_MESH_DEFAULT_TTL;
 	TAILQ_INIT(&ms->ms_known_gates);
 	TAILQ_INIT(&ms->ms_routes);
+	ms->ms_rtcount = 0;
 	MESH_RT_LOCK_INIT(ms, "MBSS");
 #if defined(__DragonFly__)
 	callout_init_mp(&ms->ms_cleantimer);