Skip to content

Commit 3bcc358

Browse files
committed
fix: limit output buffer for snappy decompression (DDoS)
1 parent 707201a commit 3bcc358

File tree

2 files changed

+32
-11
lines changed

2 files changed

+32
-11
lines changed

src/protocol/record.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use std::io::{Cursor, Read, Write};
2424
#[cfg(test)]
2525
use proptest::prelude::*;
2626

27+
use crate::protocol::vec_builder::DEFAULT_BLOCK_SIZE;
28+
2729
use super::{
2830
primitives::{Int16, Int32, Int64, Int8, Varint, Varlong},
2931
traits::{ReadError, ReadType, WriteError, WriteType},
@@ -610,16 +612,35 @@ where
610612
let uncompressed_size = decompress_len(&input).unwrap();
611613

612614
// Decode snappy payload.
613-
let mut decoder = Decoder::new();
614-
let mut output = vec![0; uncompressed_size];
615-
let actual_uncompressed_size = decoder
616-
.decompress(&input, &mut output)
617-
.map_err(|e| ReadError::Malformed(Box::new(e)))?;
618-
if actual_uncompressed_size != uncompressed_size {
619-
return Err(ReadError::Malformed(
620-
"broken snappy data".to_string().into(),
621-
));
622-
}
615+
// The uncompressed length is unchecked and can be up to 2^32-1 bytes. To avoid a DDoS vector we try to
616+
// limit it to a small size and if that fails we double that size;
617+
let mut max_uncompressed_size = DEFAULT_BLOCK_SIZE;
618+
let output = loop {
619+
let try_uncompressed_size = uncompressed_size.min(max_uncompressed_size);
620+
621+
let mut decoder = Decoder::new();
622+
let mut output = vec![0; try_uncompressed_size];
623+
let actual_uncompressed_size = match decoder.decompress(&input, &mut output) {
624+
Ok(size) => size,
625+
Err(snap::Error::BufferTooSmall { .. })
626+
if max_uncompressed_size < uncompressed_size =>
627+
{
628+
// try larger buffer
629+
max_uncompressed_size *= 2;
630+
continue;
631+
}
632+
Err(e) => {
633+
return Err(ReadError::Malformed(Box::new(e)));
634+
}
635+
};
636+
if actual_uncompressed_size != uncompressed_size {
637+
return Err(ReadError::Malformed(
638+
"broken snappy data".to_string().into(),
639+
));
640+
}
641+
642+
break output;
643+
};
623644

624645
// Read uncompressed records.
625646
let mut decoder = Cursor::new(output);

src/protocol/vec_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Helper to build a vector w/o blowing up memory.
22
33
/// Default block size (10MB).
4-
const DEFAULT_BLOCK_SIZE: usize = 1024 * 1024 * 10;
4+
pub const DEFAULT_BLOCK_SIZE: usize = 1024 * 1024 * 10;
55

66
/// Helper to build a vector w/ limited memory consumption.
77
///

0 commit comments

Comments
 (0)