From ba4027efeffb8eedffa2b9702587fe4e7b94ba88 Mon Sep 17 00:00:00 2001 From: Jared Reyes Date: Sun, 8 Feb 2026 09:56:58 +1100 Subject: [PATCH] fix(flexbuffers): harden Reader against panics from untrusted input The FlexBuffers Rust Reader had multiple code paths that could panic when processing malformed or malicious input: 1. Bounds check failures: `get_bool()`, `get_key_len()`, `read_usize()`, and `MapReader::lazy_strcmp()` used direct slice indexing (`buffer[addr..]`) which panics on out-of-bounds access. Replaced with checked `.get()` calls that return errors instead. 2. Integer overflow: `get_str()`, `get_blob()`, `get_key()`, `get_slice()`, `VectorReader::index()`, `VectorReader::get_elem_type()`, `MapReader::index_key()`, and `MapReader::usize_index()` computed `address + length` or `address + width * count` using unchecked arithmetic, which panics on overflow in debug mode and wraps in release mode. Replaced with `checked_add()` / `checked_mul()`. These panics are reachable from any code that deserializes FlexBuffers from untrusted sources (network, files, IPC), enabling denial of service. All three crash inputs found by fuzzing now return `Err` instead of panicking. Fixes https://github.com/google/flatbuffers/issues/8923 --- rust/flexbuffers/src/reader/map.rs | 26 ++++++++++++--- rust/flexbuffers/src/reader/mod.rs | 48 ++++++++++++++++++++++----- rust/flexbuffers/src/reader/vector.rs | 16 +++++++-- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/rust/flexbuffers/src/reader/map.rs b/rust/flexbuffers/src/reader/map.rs index 967509ec113..559457236aa 100644 --- a/rust/flexbuffers/src/reader/map.rs +++ b/rust/flexbuffers/src/reader/map.rs @@ -79,8 +79,11 @@ impl MapReader { // Using &CStr will eagerly compute the length of the key. &str needs length info AND utf8 // validation. This version is faster than both. fn lazy_strcmp(&self, key_addr: usize, key: &str) -> Ordering { - // TODO: Can we know this won't OOB and panic? - let k = self.buffer[key_addr..].iter().take_while(|&&b| b != b'\0'); + let tail = match self.buffer.get(key_addr..) { + Some(s) => s, + None => return Ordering::Less, + }; + let k = tail.iter().take_while(|&&b| b != b'\0'); k.cmp(key.as_bytes().iter()) } @@ -89,7 +92,9 @@ impl MapReader { let (mut low, mut high) = (0, self.length); while low < high { let i = (low + high) / 2; - let key_offset_address = self.keys_address + i * self.keys_width.n_bytes(); + let key_offset_address = i + .checked_mul(self.keys_width.n_bytes()) + .and_then(|offset| self.keys_address.checked_add(offset))?; let key_address = deref_offset(&self.buffer, key_offset_address, self.keys_width).ok()?; match self.lazy_strcmp(key_address, key) { @@ -115,8 +120,19 @@ impl MapReader { if i >= self.length { return Err(Error::IndexOutOfBounds); } - let data_address = self.values_address + self.values_width.n_bytes() * i; - let type_address = self.values_address + self.values_width.n_bytes() * self.length + i; + let data_address = self + .values_width + .n_bytes() + .checked_mul(i) + .and_then(|offset| self.values_address.checked_add(offset)) + .ok_or(Error::FlexbufferOutOfBounds)?; + let type_address = self + .values_width + .n_bytes() + .checked_mul(self.length) + .and_then(|offset| self.values_address.checked_add(offset)) + .and_then(|addr| addr.checked_add(i)) + .ok_or(Error::FlexbufferOutOfBounds)?; let (fxb_type, width) = self .buffer .get(type_address) diff --git a/rust/flexbuffers/src/reader/mod.rs b/rust/flexbuffers/src/reader/mod.rs index 5035dea6c6f..c3309f9c9d3 100644 --- a/rust/flexbuffers/src/reader/mod.rs +++ b/rust/flexbuffers/src/reader/mod.rs @@ -304,7 +304,14 @@ impl Reader { if self.bitwidth().n_bytes() != std::mem::size_of::() { self.expect_bw(T::WIDTH)?; } - let end = self.address + self.length() * std::mem::size_of::(); + let byte_len = self + .length() + .checked_mul(std::mem::size_of::()) + .ok_or(Error::FlexbufferOutOfBounds)?; + let end = self + .address + .checked_add(byte_len) + .ok_or(Error::FlexbufferOutOfBounds)?; let slice: &[u8] = self.buffer.get(self.address..end).ok_or(Error::FlexbufferOutOfBounds)?; @@ -323,7 +330,12 @@ impl Reader { /// Otherwise Returns error. pub fn get_bool(&self) -> Result { self.expect_type(FlexBufferType::Bool)?; - Ok(self.buffer[self.address..self.address + self.width.n_bytes()].iter().any(|&b| b != 0)) + let end = self.address + self.width.n_bytes(); + let slice = self + .buffer + .get(self.address..end) + .ok_or(Error::FlexbufferOutOfBounds)?; + Ok(slice.iter().any(|&b| b != 0)) } /// Gets the length of the key if this type is a key. @@ -332,7 +344,11 @@ impl Reader { #[inline] fn get_key_len(&self) -> Result { self.expect_type(FlexBufferType::Key)?; - let (length, _) = self.buffer[self.address..] + let tail = self + .buffer + .get(self.address..) + .ok_or(Error::FlexbufferOutOfBounds)?; + let (length, _) = tail .iter() .enumerate() .find(|(_, &b)| b == b'\0') @@ -342,18 +358,27 @@ impl Reader { /// Retrieves the string value up until the first `\0` character. pub fn get_key(&self) -> Result { + let key_len = self.get_key_len()?; + let end = self + .address + .checked_add(key_len) + .ok_or(Error::FlexbufferOutOfBounds)?; let bytes = self .buffer - .slice(self.address..self.address + self.get_key_len()?) + .slice(self.address..end) .ok_or(Error::IndexOutOfBounds)?; Ok(bytes.buffer_str()?) } pub fn get_blob(&self) -> Result, Error> { self.expect_type(FlexBufferType::Blob)?; + let end = self + .address + .checked_add(self.length()) + .ok_or(Error::FlexbufferOutOfBounds)?; Ok(Blob( self.buffer - .slice(self.address..self.address + self.length()) + .slice(self.address..end) .ok_or(Error::IndexOutOfBounds)?, )) } @@ -366,7 +391,11 @@ impl Reader { /// is out of bounds. pub fn get_str(&self) -> Result { self.expect_type(FlexBufferType::String)?; - let bytes = self.buffer.slice(self.address..self.address + self.length()); + let end = self + .address + .checked_add(self.length()) + .ok_or(Error::FlexbufferOutOfBounds)?; + let bytes = self.buffer.slice(self.address..end); Ok(bytes.ok_or(Error::ReadUsizeOverflowed)?.buffer_str()?) } @@ -601,9 +630,12 @@ fn f64_from_le_bytes(bytes: [u8; 8]) -> f64 { } fn read_usize(buffer: &[u8], address: usize, width: BitWidth) -> usize { - let cursor = &buffer[address..]; + let cursor = match buffer.get(address..) { + Some(c) => c, + None => return 0, + }; match width { - BitWidth::W8 => cursor[0] as usize, + BitWidth::W8 => cursor.first().copied().unwrap_or_default() as usize, BitWidth::W16 => cursor .get(0..2) .and_then(|s| s.try_into().ok()) diff --git a/rust/flexbuffers/src/reader/vector.rs b/rust/flexbuffers/src/reader/vector.rs index f96cc36bcfe..c1a0d555426 100644 --- a/rust/flexbuffers/src/reader/vector.rs +++ b/rust/flexbuffers/src/reader/vector.rs @@ -52,10 +52,14 @@ impl VectorReader { if let Some(ty) = self.reader.fxb_type.typed_vector_type() { Ok((ty, self.reader.width)) } else { - let types_addr = self.reader.address + self.length * self.reader.width.n_bytes(); + let types_addr = self + .length + .checked_mul(self.reader.width.n_bytes()) + .and_then(|offset| self.reader.address.checked_add(offset)) + .ok_or(Error::FlexbufferOutOfBounds)?; self.reader .buffer - .get(types_addr + i) + .get(types_addr.checked_add(i).ok_or(Error::FlexbufferOutOfBounds)?) .ok_or(Error::FlexbufferOutOfBounds) .and_then(|&t| unpack_type(t)) } @@ -70,7 +74,13 @@ impl VectorReader { return Err(Error::IndexOutOfBounds); } let (fxb_type, bw) = self.get_elem_type(i)?; - let data_address = self.reader.address + self.reader.width.n_bytes() * i; + let data_address = self + .reader + .width + .n_bytes() + .checked_mul(i) + .and_then(|offset| self.reader.address.checked_add(offset)) + .ok_or(Error::FlexbufferOutOfBounds)?; Reader::new( self.reader.buffer.shallow_copy(), data_address,