DF-0350 / fix.diff
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); |