From 4a0a098f5ef62759791a7c868acb0d1a249a0122 Mon Sep 17 00:00:00 2001 From: iho Date: Thu, 9 Jul 2026 12:05:10 +0300 Subject: [PATCH] fix: bound MerkleProof path length on deserialize MerkleProof::read trusted path_len from the peer/API and called Vec::with_capacity without a cap, so a huge path_len could panic with capacity overflow or force a large allocation. Reject path lengths above 64 (well beyond any legitimate O(log n) proof) and cap preallocation. Also apply the same 100k single-read limit to StreamingReader that BinReader already enforces. Related to segment decode bounds (#3827 / #3850). --- core/src/core/merkle_proof.rs | 16 +++++++++++++- core/src/ser.rs | 4 ++++ core/tests/merkle_proof.rs | 39 +++++++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/core/src/core/merkle_proof.rs b/core/src/core/merkle_proof.rs index f867fd6b2c..46fdaff9ce 100644 --- a/core/src/core/merkle_proof.rs +++ b/core/src/core/merkle_proof.rs @@ -18,6 +18,7 @@ use crate::core::hash::Hash; use crate::core::pmmr; use crate::ser; use crate::ser::{PMMRIndexHashable, Readable, Reader, Writeable, Writer}; +use std::cmp::min; use util::ToHex; /// Merkle proof errors. @@ -27,6 +28,15 @@ pub enum MerkleProofError { RootMismatch, } +/// Maximum sibling path length accepted when deserializing a Merkle proof. +/// +/// Path length is O(log n) for an MMR of size n (plus a few peak hashes). +/// 64 is far larger than any legitimate proof and prevents a malicious +/// `path_len` from causing a capacity overflow / huge allocation. +pub const MAX_MERKLE_PROOF_PATH: u64 = 64; + +const MERKLE_PROOF_PATH_PREALLOC: u64 = 16; + /// A Merkle proof that proves a particular element exists in the MMR. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, PartialOrd, Ord)] pub struct MerkleProof { @@ -50,7 +60,11 @@ impl Readable for MerkleProof { fn read(reader: &mut R) -> Result { let mmr_size = reader.read_u64()?; let path_len = reader.read_u64()?; - let mut path = Vec::with_capacity(path_len as usize); + if path_len > MAX_MERKLE_PROOF_PATH { + return Err(ser::Error::TooLargeReadErr); + } + // Cap preallocation even when path_len is within the allowed range. + let mut path = Vec::with_capacity(min(path_len, MERKLE_PROOF_PATH_PREALLOC) as usize); for _ in 0..path_len { let hash = Hash::read(reader)?; path.push(hash); diff --git a/core/src/ser.rs b/core/src/ser.rs index 38cd9aa1b7..74e6b0e6fe 100644 --- a/core/src/ser.rs +++ b/core/src/ser.rs @@ -595,6 +595,10 @@ impl<'a> Reader for StreamingReader<'a> { /// Read a fixed number of bytes. fn read_fixed_bytes(&mut self, len: usize) -> Result, Error> { + // Match BinReader / BufReader: refuse huge single reads that would OOM or panic. + if len > 100_000 { + return Err(Error::TooLargeReadErr); + } let mut buf = vec![0u8; len]; self.stream.read_exact(&mut buf)?; self.total_bytes_read += len as u64; diff --git a/core/tests/merkle_proof.rs b/core/tests/merkle_proof.rs index 09a57d80fd..f44d9e9091 100644 --- a/core/tests/merkle_proof.rs +++ b/core/tests/merkle_proof.rs @@ -14,12 +14,16 @@ mod common; -use self::core::core::merkle_proof::MerkleProof; +use self::core::core::merkle_proof::{MerkleProof, MAX_MERKLE_PROOF_PATH}; use self::core::core::pmmr::{ReadablePMMR, VecBackend, PMMR}; -use self::core::ser::{self, PMMRIndexHashable}; +use self::core::ser::{self, DeserializationMode, PMMRIndexHashable, ProtocolVersion}; use crate::common::TestElem; use grin_core as core; +fn push_u64(buf: &mut Vec, v: u64) { + buf.extend_from_slice(&v.to_be_bytes()); +} + #[test] fn empty_merkle_proof() { let proof = MerkleProof::empty(); @@ -27,6 +31,37 @@ fn empty_merkle_proof() { assert_eq!(proof.mmr_size, 0); } +#[test] +fn merkle_proof_read_rejects_huge_path_len() { + // mmr_size=0, path_len just over the allowed maximum → TooLargeReadErr + // before any allocation of path entries. + let mut bytes = vec![]; + push_u64(&mut bytes, 0); + push_u64(&mut bytes, MAX_MERKLE_PROOF_PATH + 1); + + let res: Result = ser::deserialize( + &mut &bytes[..], + ProtocolVersion(1), + DeserializationMode::default(), + ); + assert_eq!(res.err(), Some(ser::Error::TooLargeReadErr)); +} + +#[test] +fn merkle_proof_read_rejects_capacity_overflow_path_len() { + // Historical bug: path_len near u64::MAX caused Vec::with_capacity to panic. + let mut bytes = vec![]; + push_u64(&mut bytes, 1); + push_u64(&mut bytes, u64::MAX); + + let res: Result = ser::deserialize( + &mut &bytes[..], + ProtocolVersion(1), + DeserializationMode::default(), + ); + assert_eq!(res.err(), Some(ser::Error::TooLargeReadErr)); +} + #[test] fn merkle_proof_ser_deser() { let mut ba = VecBackend::new();