Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion core/src/core/merkle_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -50,7 +60,11 @@ impl Readable for MerkleProof {
fn read<R: Reader>(reader: &mut R) -> Result<MerkleProof, ser::Error> {
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);
Expand Down
4 changes: 4 additions & 0 deletions core/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>, 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;
Expand Down
39 changes: 37 additions & 2 deletions core/tests/merkle_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,54 @@

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<u8>, v: u64) {
buf.extend_from_slice(&v.to_be_bytes());
}

#[test]
fn empty_merkle_proof() {
let proof = MerkleProof::empty();
assert_eq!(proof.path, vec![]);
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<MerkleProof, _> = 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<MerkleProof, _> = 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();
Expand Down
Loading