From ee3c5e761e676aa4089b275b9b0e50b07cc49cdc Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:17:32 +0000 Subject: [PATCH] =?UTF-8?q?geoip:=20bound=20recursion=20+=20require=20forw?= =?UTF-8?q?ard=20progress=20in=20the=20.mmdb=20value=20decoder=20=E2=80=94?= =?UTF-8?q?=20value=5Flen/map=5Fget=20recursed=20into=20nested=20maps/arra?= =?UTF-8?q?ys=20with=20no=20depth=20limit=20(unlike=20resolve),=20so=20a?= =?UTF-8?q?=20crafted=20database=20could=20overflow=20the=20stack=20or=20s?= =?UTF-8?q?pin=20on=20a=20zero-advance=20value;=20cap=20nesting=20at=2032?= =?UTF-8?q?=20and=20bail=20on=20a=20malformed=20(0-length)=20sub-value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/geoip.rs | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/modules/geoip.rs b/src/modules/geoip.rs index 7a22387..bf50872 100644 --- a/src/modules/geoip.rs +++ b/src/modules/geoip.rs @@ -15,6 +15,8 @@ use crate::Uid; const MARKER: &[u8] = b"\xab\xcd\xefMaxMind.com"; const SEPARATOR: usize = 16; +/// Recursion cap for the typed-value decoder (see `Mmdb::value_len`). +const MAX_MMDB_DEPTH: u8 = 32; /// A loaded MaxMind DB, cached in `Server.ext`. pub struct GeoDb(pub Arc); @@ -102,7 +104,16 @@ impl<'a> Decoder<'a> { /// The number of bytes the value at `off` occupies (pointers are 2–5 bytes; not /// followed). Recurses into maps/arrays. Returns 0 on malformed input. - fn value_len(&self, off: usize) -> usize { + /// + /// `MAX_MMDB_DEPTH` caps nesting — real GeoIP records are 3–4 deep; the cap only + /// exists so a hostile file can't overflow the stack. + fn value_len(&self, off: usize, depth: u8) -> usize { + // Bound recursion (a crafted, deeply-nested .mmdb would otherwise overflow the + // stack). A valid value is never 0 bytes, so 0 doubles as a "malformed" signal + // callers bail on — which also stops a huge `size` from spinning with no progress. + if depth > MAX_MMDB_DEPTH { + return 0; + } let b = match self.data.get(off) { Some(&b) => b, None => return 0, @@ -119,8 +130,12 @@ impl<'a> Decoder<'a> { // map: `size` key/value pairs let mut cur = payload; for _ in 0..size { - cur += self.value_len(cur); // key - cur += self.value_len(cur); // value + let k = self.value_len(cur, depth + 1); // key + let v = self.value_len(cur + k, depth + 1); // value + if k == 0 || v == 0 { + return 0; // malformed or too deep + } + cur += k + v; } cur - off } @@ -128,7 +143,11 @@ impl<'a> Decoder<'a> { // array: `size` elements let mut cur = payload; for _ in 0..size { - cur += self.value_len(cur); + let v = self.value_len(cur, depth + 1); + if v == 0 { + return 0; + } + cur += v; } cur - off } @@ -169,11 +188,19 @@ impl<'a> Decoder<'a> { let mut cur = payload; for _ in 0..size { let k = self.string(cur)?; - cur += self.value_len(cur); + let klen = self.value_len(cur, 0); + if klen == 0 { + return None; // malformed + } + cur += klen; if k == key { return Some(cur); } - cur += self.value_len(cur); + let vlen = self.value_len(cur, 0); + if vlen == 0 { + return None; + } + cur += vlen; } None }