From 83cdce17c5469bb3898597e42c1670e44af1ba31 Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:12:39 +0000 Subject: [PATCH] =?UTF-8?q?connflood:=20bound=20the=20per-IP=20connection-?= =?UTF-8?q?history=20map=20(prune=20stale=20buckets=20past=2065536=20track?= =?UTF-8?q?ed=20IPs)=20=E2=80=94=20it=20only=20shrank=20on=20the=20tick=20?= =?UTF-8?q?GC,=20so=20a=20wide=20source-IP=20spread=20could=20grow=20it=20?= =?UTF-8?q?unbounded=20between=20ticks=20(memory=20DoS)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/connflood.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/modules/connflood.rs b/src/modules/connflood.rs index b8337c6..ce312ad 100644 --- a/src/modules/connflood.rs +++ b/src/modules/connflood.rs @@ -32,17 +32,24 @@ pub fn over_limit(s: &mut Server, ip: IpAddr) -> bool { return false; }; let n = now(); - let hist = s - .ext - .get_or_insert_with::(ConnHistory::default) - .0 - .entry(ip) - .or_default(); + let store = s.ext.get_or_insert_with::(ConnHistory::default); + // Bound memory: a wide source-IP spread (e.g. an IPv6 /64) could otherwise grow + // this map unbounded between tick GCs — once it's large, drop stale buckets now. + if store.0.len() > MAX_TRACKED_IPS { + store.0.retain(|_, times| { + 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.push(n); 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. pub struct ConnFlood; impl Module for ConnFlood {