Stop untrusted preallocation during string deserialization
This is an easy memory denial of service attack.
This commit is contained in:
parent
db2f920d96
commit
609d70ae53
|
@ -21,6 +21,7 @@ pub use header::BlockTimeError;
|
||||||
pub use header::{CountedHeader, Header};
|
pub use header::{CountedHeader, Header};
|
||||||
pub use height::Height;
|
pub use height::Height;
|
||||||
pub use root_hash::RootHash;
|
pub use root_hash::RootHash;
|
||||||
|
pub use serialize::MAX_BLOCK_BYTES;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@ use super::SerializationError;
|
||||||
pub trait ReadZcashExt: io::Read {
|
pub trait ReadZcashExt: io::Read {
|
||||||
/// Reads a `u64` using the Bitcoin `CompactSize` encoding.
|
/// Reads a `u64` using the Bitcoin `CompactSize` encoding.
|
||||||
///
|
///
|
||||||
|
/// # Security
|
||||||
|
///
|
||||||
|
/// Deserialized sizes must be validated before being used.
|
||||||
|
///
|
||||||
|
/// Preallocating vectors using untrusted `CompactSize`s allows memory
|
||||||
|
/// denial of service attacks. Valid sizes must be less than
|
||||||
|
/// `MAX_BLOCK_BYTES / min_serialized_item_bytes` (or a lower limit
|
||||||
|
/// specified by the Zcash consensus rules or Bitcoin network protocol).
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
@ -87,15 +96,6 @@ pub trait ReadZcashExt: io::Read {
|
||||||
Ok(SocketAddr::new(ip_addr, port))
|
Ok(SocketAddr::new(ip_addr, port))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a Bitcoin-encoded UTF-8 string.
|
|
||||||
#[inline]
|
|
||||||
fn read_string(&mut self) -> Result<String, SerializationError> {
|
|
||||||
let len = self.read_compactsize()?;
|
|
||||||
let mut buf = vec![0; len as usize];
|
|
||||||
self.read_exact(&mut buf)?;
|
|
||||||
String::from_utf8(buf).map_err(|_| SerializationError::Parse("invalid utf-8"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convenience method to read a `[u8; 4]`.
|
/// Convenience method to read a `[u8; 4]`.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> {
|
fn read_4_bytes(&mut self) -> io::Result<[u8; 4]> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use super::{ReadZcashExt, SerializationError};
|
use super::{ReadZcashExt, SerializationError};
|
||||||
|
use byteorder::ReadBytesExt;
|
||||||
|
|
||||||
/// Consensus-critical serialization for Zcash.
|
/// Consensus-critical serialization for Zcash.
|
||||||
///
|
///
|
||||||
|
@ -33,6 +34,21 @@ impl<T: ZcashDeserialize> ZcashDeserialize for Vec<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a byte.
|
||||||
|
impl ZcashDeserialize for u8 {
|
||||||
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
|
Ok(reader.read_u8()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a Bitcoin-encoded UTF-8 string.
|
||||||
|
impl ZcashDeserialize for String {
|
||||||
|
fn zcash_deserialize<R: io::Read>(reader: R) -> Result<Self, SerializationError> {
|
||||||
|
let bytes: Vec<_> = Vec::zcash_deserialize(reader)?;
|
||||||
|
String::from_utf8(bytes).map_err(|_| SerializationError::Parse("invalid utf-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper for deserializing more succinctly via type inference
|
/// Helper for deserializing more succinctly via type inference
|
||||||
pub trait ZcashDeserializeInto {
|
pub trait ZcashDeserializeInto {
|
||||||
/// Deserialize based on type inference
|
/// Deserialize based on type inference
|
||||||
|
|
|
@ -44,25 +44,27 @@ impl<P: ZkSnarkProof> ZcashSerialize for JoinSplitData<P> {
|
||||||
|
|
||||||
impl<P: ZkSnarkProof> ZcashDeserialize for Option<JoinSplitData<P>> {
|
impl<P: ZkSnarkProof> ZcashDeserialize for Option<JoinSplitData<P>> {
|
||||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||||
let joinsplits: Vec<sprout::JoinSplit<P>> = Vec::zcash_deserialize(&mut reader)?;
|
let num_joinsplits = reader.read_compactsize()?;
|
||||||
|
match num_joinsplits {
|
||||||
if joinsplits.is_empty() {
|
0 => Ok(None),
|
||||||
Ok(None)
|
n => {
|
||||||
} else {
|
let first = sprout::JoinSplit::zcash_deserialize(&mut reader)?;
|
||||||
let (first, rest) = joinsplits
|
let mut rest = Vec::with_capacity((n - 1) as usize);
|
||||||
.split_first()
|
for _ in 0..(n - 1) {
|
||||||
.expect("a non-empty Vec must have at least one entry");
|
rest.push(sprout::JoinSplit::zcash_deserialize(&mut reader)?);
|
||||||
|
}
|
||||||
let pub_key = reader.read_32_bytes()?.into();
|
let pub_key = reader.read_32_bytes()?.into();
|
||||||
let sig = reader.read_64_bytes()?.into();
|
let sig = reader.read_64_bytes()?.into();
|
||||||
Ok(Some(JoinSplitData {
|
Ok(Some(JoinSplitData {
|
||||||
first: first.clone(),
|
first,
|
||||||
rest: rest.to_vec(),
|
rest,
|
||||||
pub_key,
|
pub_key,
|
||||||
sig,
|
sig,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZcashSerialize for Transaction {
|
impl ZcashSerialize for Transaction {
|
||||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||||
|
|
|
@ -464,7 +464,7 @@ impl Codec {
|
||||||
reader.read_socket_addr()?,
|
reader.read_socket_addr()?,
|
||||||
),
|
),
|
||||||
nonce: Nonce(reader.read_u64::<LittleEndian>()?),
|
nonce: Nonce(reader.read_u64::<LittleEndian>()?),
|
||||||
user_agent: reader.read_string()?,
|
user_agent: String::zcash_deserialize(&mut reader)?,
|
||||||
start_height: block::Height(reader.read_u32::<LittleEndian>()?),
|
start_height: block::Height(reader.read_u32::<LittleEndian>()?),
|
||||||
relay: match reader.read_u8()? {
|
relay: match reader.read_u8()? {
|
||||||
0 => false,
|
0 => false,
|
||||||
|
@ -488,7 +488,7 @@ impl Codec {
|
||||||
|
|
||||||
fn read_reject<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
|
fn read_reject<R: Read>(&self, mut reader: R) -> Result<Message, Error> {
|
||||||
Ok(Message::Reject {
|
Ok(Message::Reject {
|
||||||
message: reader.read_string()?,
|
message: String::zcash_deserialize(&mut reader)?,
|
||||||
ccode: match reader.read_u8()? {
|
ccode: match reader.read_u8()? {
|
||||||
0x01 => RejectReason::Malformed,
|
0x01 => RejectReason::Malformed,
|
||||||
0x10 => RejectReason::Invalid,
|
0x10 => RejectReason::Invalid,
|
||||||
|
@ -501,7 +501,7 @@ impl Codec {
|
||||||
0x50 => RejectReason::Other,
|
0x50 => RejectReason::Other,
|
||||||
_ => return Err(Error::Parse("invalid RejectReason value in ccode field")),
|
_ => return Err(Error::Parse("invalid RejectReason value in ccode field")),
|
||||||
},
|
},
|
||||||
reason: reader.read_string()?,
|
reason: String::zcash_deserialize(&mut reader)?,
|
||||||
// Sometimes there's data, sometimes there isn't. There's no length
|
// Sometimes there's data, sometimes there isn't. There's no length
|
||||||
// field, this is just implicitly encoded by the body_len.
|
// field, this is just implicitly encoded by the body_len.
|
||||||
// Apparently all existing implementations only supply 32 bytes of
|
// Apparently all existing implementations only supply 32 bytes of
|
||||||
|
|
Loading…
Reference in New Issue