mirror of
https://seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git
synced 2026-10-18 19:10:30 +08:00
protocol: Limit allocations when decoding frames
`VarInt` can get large enough to exhaust available memory on many machines. Instead of allocating directly, check the size of the allocation against some limit. The limit chosen for frames is 128 KiB, which is roughly twice as much as actual gossip and Git protocol frames are expected to be. However, 128 KiB is arguably short enough to not burden a standard consumer machine too much.
This commit is contained in:
@@ -58,7 +58,18 @@ impl<const B: usize, D: wire::Decode> Deserializer<B, D> {
|
||||
|
||||
Ok(Some(msg))
|
||||
}
|
||||
Err(wire::Error::UnexpectedEnd { .. }) => Ok(None),
|
||||
Err(wire::Error::UnexpectedEnd { .. }) => {
|
||||
log::debug!("Dropping incomplete frame: Expected more bytes.");
|
||||
Ok(None)
|
||||
}
|
||||
Err(wire::Error::FrameTooLong { length, limit }) => {
|
||||
log::debug!(
|
||||
"Dropping frame that is too large: Expected at most {} bytes, got {} bytes.",
|
||||
limit,
|
||||
length
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
Err(wire::Error::Invalid(err)) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
Invalid(#[from] Invalid),
|
||||
|
||||
#[error("frame too long: expected at most {limit}, got {length}")]
|
||||
FrameTooLong { length: usize, limit: usize },
|
||||
|
||||
#[error(
|
||||
"unexpected end of buffer, requested {requested} more bytes but only {available} are available"
|
||||
)]
|
||||
@@ -147,7 +150,7 @@ pub trait Decode: Sized {
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
Err(err @ Error::UnexpectedEnd { .. }) => {
|
||||
Err(err @ (Error::UnexpectedEnd { .. } | Error::FrameTooLong { .. })) => {
|
||||
panic!("{}", err);
|
||||
}
|
||||
Err(Error::Invalid(e)) => Err(e),
|
||||
|
||||
@@ -210,6 +210,9 @@ pub struct Frame<M = Message> {
|
||||
}
|
||||
|
||||
impl<M> Frame<M> {
|
||||
/// The maximum length of a frame, in bytes (128 KiB).
|
||||
pub const LENGTH_LIMIT: usize = 1 << 17;
|
||||
|
||||
/// Create a 'git' protocol frame.
|
||||
pub fn git(stream: StreamId, data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
@@ -360,7 +363,7 @@ impl<M: wire::Decode> wire::Decode for Frame<M> {
|
||||
Ok(frame)
|
||||
}
|
||||
Ok(StreamType::Gossip) => {
|
||||
let data = varint::payload::decode(buf)?;
|
||||
let data = varint::payload::decode(buf, Frame::<M>::LENGTH_LIMIT)?;
|
||||
let mut cursor = io::Cursor::new(data);
|
||||
let msg = M::decode(&mut cursor)?;
|
||||
let frame = Frame {
|
||||
@@ -375,7 +378,7 @@ impl<M: wire::Decode> wire::Decode for Frame<M> {
|
||||
Ok(frame)
|
||||
}
|
||||
Ok(StreamType::Git) => {
|
||||
let data = varint::payload::decode(buf)?;
|
||||
let data = varint::payload::decode(buf, Frame::<M>::LENGTH_LIMIT)?;
|
||||
Ok(Frame::git(stream, data))
|
||||
}
|
||||
Err(n) => Err(wire::Invalid::StreamType { actual: n }.into()),
|
||||
|
||||
@@ -163,10 +163,21 @@ pub mod payload {
|
||||
buf.put_slice(payload);
|
||||
}
|
||||
|
||||
/// Decode varint-prefixed data payload.
|
||||
pub fn decode(buf: &mut impl Buf) -> Result<Vec<u8>, wire::Error> {
|
||||
/// Decode varint-prefixed data payload, if the size of the payload
|
||||
/// is at most `limit` bytes.
|
||||
pub fn decode(buf: &mut impl Buf, limit: usize) -> Result<Vec<u8>, wire::Error> {
|
||||
let size = VarInt::decode(buf)?;
|
||||
let mut data = vec![0; *size as usize];
|
||||
|
||||
let size = *size as usize;
|
||||
|
||||
if size > limit {
|
||||
return Err(wire::Error::FrameTooLong {
|
||||
length: size,
|
||||
limit,
|
||||
});
|
||||
}
|
||||
|
||||
let mut data = vec![0; size];
|
||||
buf.try_copy_to_slice(&mut data[..])?;
|
||||
|
||||
Ok(data)
|
||||
|
||||
Reference in New Issue
Block a user