connflood: bound the per-IP connection-history map (prune stale buckets past 65536 tracked IPs) — it only shrank on the tick GC, so a wide source-IP spread could grow it unbounded between ticks (memory DoS)

This commit is contained in:
Jean Chevronnet 2026-08-19 01:12:39 +00:00
parent a218d65371
commit 83cdce17c5

View file

@ -32,17 +32,24 @@ pub fn over_limit(s: &mut Server, ip: IpAddr) -> bool {
return false; return false;
}; };
let n = now(); let n = now();
let hist = s let store = s.ext.get_or_insert_with::<ConnHistory>(ConnHistory::default);
.ext // Bound memory: a wide source-IP spread (e.g. an IPv6 /64) could otherwise grow
.get_or_insert_with::<ConnHistory>(ConnHistory::default) // this map unbounded between tick GCs — once it's large, drop stale buckets now.
.0 if store.0.len() > MAX_TRACKED_IPS {
.entry(ip) store.0.retain(|_, times| {
.or_default(); times.retain(|&t| n.saturating_sub(t) < secs);
!times.is_empty()
});
}
let hist = store.0.entry(ip).or_default();
hist.retain(|&t| n.saturating_sub(t) < secs); hist.retain(|&t| n.saturating_sub(t) < secs);
hist.push(n); hist.push(n);
hist.len() as u32 > max hist.len() as u32 > max
} }
/// Ceiling on distinct source IPs tracked between GC ticks (memory bound).
const MAX_TRACKED_IPS: usize = 65_536;
/// Prunes stale per-IP bookkeeping on the tick. /// Prunes stale per-IP bookkeeping on the tick.
pub struct ConnFlood; pub struct ConnFlood;
impl Module for ConnFlood { impl Module for ConnFlood {