Fix Readable impl for Transaction (#795)
Fuzz test demonstrated that the current implemenation panics on reading from the network when serialized tx. Contains for example huge number of input or outputs. It allows a mailicious sender to kill the server.
This commit is contained in:
committed by
Ignotus Peverell
parent
cb71386097
commit
1bad7188b7
@@ -88,6 +88,15 @@ pub const MAX_BLOCK_WEIGHT: usize = 80_000;
|
||||
/// Hundreds of inputs + 1 output might be slow to validate (issue#258)
|
||||
pub const MAX_BLOCK_INPUTS: usize = 300_000; // soft fork down when too_high
|
||||
|
||||
/// Maixmum inputs for a transaction
|
||||
pub const MAX_TX_INPUTS: u64 = 500;
|
||||
|
||||
/// Maixmum outputs for a transaction
|
||||
pub const MAX_TX_OUTPUTS: u64 = 500; // wallet uses 500 as max
|
||||
|
||||
/// Maixmum kernels for a transaction
|
||||
pub const MAX_TX_KERNELS: u64 = 2048;
|
||||
|
||||
/// Whether a block exceeds the maximum acceptable weight
|
||||
pub fn exceeds_weight(input_len: usize, output_len: usize, kernel_len: usize) -> bool {
|
||||
input_len * BLOCK_INPUT_WEIGHT + output_len * BLOCK_OUTPUT_WEIGHT
|
||||
|
||||
@@ -90,6 +90,12 @@ where
|
||||
fn dump_stats(&self);
|
||||
}
|
||||
|
||||
/// Maixmum peeks for a Merkle proof
|
||||
pub const MAX_PEAKS: u64 = 300_000;
|
||||
|
||||
/// Maixmum path for a Merkle proof
|
||||
pub const MAX_PATH: u64 = 300_000;
|
||||
|
||||
/// A Merkle proof.
|
||||
/// Proves inclusion of an output (node) in the output MMR.
|
||||
/// We can use this to prove an output was unspent at the time of a given block
|
||||
@@ -134,6 +140,10 @@ impl Readable for MerkleProof {
|
||||
|
||||
let (peaks_len, path_len) = ser_multiread!(reader, read_u64, read_u64);
|
||||
|
||||
if peaks_len > MAX_PEAKS || path_len > MAX_PATH {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
|
||||
let mut peaks = Vec::with_capacity(peaks_len as usize);
|
||||
for _ in 0..peaks_len {
|
||||
peaks.push(Hash::read(reader)?);
|
||||
|
||||
@@ -302,6 +302,12 @@ impl Readable for Transaction {
|
||||
let (input_len, output_len, kernel_len) =
|
||||
ser_multiread!(reader, read_u64, read_u64, read_u64);
|
||||
|
||||
if input_len > consensus::MAX_TX_INPUTS
|
||||
|| output_len > consensus::MAX_TX_OUTPUTS
|
||||
|| kernel_len > consensus::MAX_TX_KERNELS {
|
||||
return Err(ser::Error::CorruptedData)
|
||||
}
|
||||
|
||||
let inputs = read_and_verify_sorted(reader, input_len)?;
|
||||
let outputs = read_and_verify_sorted(reader, output_len)?;
|
||||
let kernels = read_and_verify_sorted(reader, kernel_len)?;
|
||||
|
||||
+5
-1
@@ -19,7 +19,7 @@
|
||||
//! To use it simply implement `Writeable` or `Readable` and then use the
|
||||
//! `serialize` or `deserialize` functions on them as appropriate.
|
||||
|
||||
use std::{cmp, error, fmt};
|
||||
use std::{cmp, error, fmt, mem};
|
||||
use std::io::{self, Read, Write};
|
||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
|
||||
use keychain::{BlindingFactor, Identifier, IDENTIFIER_SIZE};
|
||||
@@ -219,6 +219,10 @@ pub fn read_and_verify_sorted<T>(reader: &mut Reader, count: u64) -> Result<Vec<
|
||||
where
|
||||
T: Readable + Hashed + Writeable,
|
||||
{
|
||||
let elem_size = mem::size_of::<T>();
|
||||
if count.checked_mul(elem_size as u64).is_none() {
|
||||
return Err(Error::TooLargeReadErr);
|
||||
}
|
||||
let result: Vec<T> = try!((0..count).map(|_| T::read(reader)).collect());
|
||||
result.verify_sort_order()?;
|
||||
Ok(result)
|
||||
|
||||
Reference in New Issue
Block a user