geoip: bound recursion + require forward progress in the .mmdb value decoder — value_len/map_get recursed into nested maps/arrays with no depth limit (unlike resolve), so a crafted database could overflow the stack or spin on a zero-advance value; cap nesting at 32 and bail on a malformed (0-length) sub-value
This commit is contained in:
parent
e200779d27
commit
ee3c5e761e
1 changed files with 33 additions and 6 deletions
|
|
@ -15,6 +15,8 @@ use crate::Uid;
|
||||||
|
|
||||||
const MARKER: &[u8] = b"\xab\xcd\xefMaxMind.com";
|
const MARKER: &[u8] = b"\xab\xcd\xefMaxMind.com";
|
||||||
const SEPARATOR: usize = 16;
|
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`.
|
/// A loaded MaxMind DB, cached in `Server.ext`.
|
||||||
pub struct GeoDb(pub Arc<Mmdb>);
|
pub struct GeoDb(pub Arc<Mmdb>);
|
||||||
|
|
@ -102,7 +104,16 @@ impl<'a> Decoder<'a> {
|
||||||
|
|
||||||
/// The number of bytes the value at `off` occupies (pointers are 2–5 bytes; not
|
/// 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.
|
/// 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) {
|
let b = match self.data.get(off) {
|
||||||
Some(&b) => b,
|
Some(&b) => b,
|
||||||
None => return 0,
|
None => return 0,
|
||||||
|
|
@ -119,8 +130,12 @@ impl<'a> Decoder<'a> {
|
||||||
// map: `size` key/value pairs
|
// map: `size` key/value pairs
|
||||||
let mut cur = payload;
|
let mut cur = payload;
|
||||||
for _ in 0..size {
|
for _ in 0..size {
|
||||||
cur += self.value_len(cur); // key
|
let k = self.value_len(cur, depth + 1); // key
|
||||||
cur += self.value_len(cur); // value
|
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
|
cur - off
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +143,11 @@ impl<'a> Decoder<'a> {
|
||||||
// array: `size` elements
|
// array: `size` elements
|
||||||
let mut cur = payload;
|
let mut cur = payload;
|
||||||
for _ in 0..size {
|
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
|
cur - off
|
||||||
}
|
}
|
||||||
|
|
@ -169,11 +188,19 @@ impl<'a> Decoder<'a> {
|
||||||
let mut cur = payload;
|
let mut cur = payload;
|
||||||
for _ in 0..size {
|
for _ in 0..size {
|
||||||
let k = self.string(cur)?;
|
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 {
|
if k == key {
|
||||||
return Some(cur);
|
return Some(cur);
|
||||||
}
|
}
|
||||||
cur += self.value_len(cur);
|
let vlen = self.value_len(cur, 0);
|
||||||
|
if vlen == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
cur += vlen;
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue