CVE-2022-35922: Allocation of Resources Without Limits or Throttling (CWE-770)
value: Rust-WebSocket is a WebSocket (RFC6455) library written in Rust. In versions prior to 0.26.5 untrusted websocket connections can cause an out-of-memory (OOM) process abort in a client or a server. The root cause of the issue is during dataframe parsing. Affected versions would allocate a buffer based on the declared dataframe size, which may come from an untrusted source. When `Vec::with_capacity` fails to allocate, the default Rust allocator will abort the current process, killing all threads. This affects only sync (non-Tokio) implementation. Async version also does not limit memory, but does not use `with_capacity`, so DoS can happen only when bytes for oversized dataframe or message actually got delivered by the attacker. The crashes are fixed in version 0.26.5 by imposing default dataframe size limits. Affected users are advised to update to this version. Users unable to upgrade are advised to filter websocket traffic externally or to only accept trusted traffic.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2022-08-01T22:15Z
Repository: https://github.com/websockets-rs/rust-websocket
Language: Rust
Files changed: src/receiver.rs, websocket-base/src/dataframe.rs
Commit date: 2022-07-24 01:50:07 +0200
Commit message: Implement dataframe and message size limits

Diff: diff --git a/src/receiver.rs b/src/receiver.rs
index f74153c..9ab5593 100644
--- a/src/receiver.rs
+++ b/src/receiver.rs
@@ -12,10 +12,14 @@ pub use crate::stream::sync::Shutdown;
 use crate::stream::sync::{AsTcpStream, Stream};
 use crate::ws;
 use crate::ws::receiver::Receiver as ReceiverTrait;
 use crate::ws::receiver::{DataFrameIterator, MessageIterator};
 
+const DEFAULT_MAX_DATAFRAME_SIZE : usize = 1024*1024*100;
+const DEFAULT_MAX_MESSAGE_SIZE : usize = 1024*1024*200;
+const MAX_DATAFRAMES_IN_ONE_MESSAGE: usize = 1024*1024;
+
 /// This reader bundles an existing stream with a parsing algorithm.
 /// It is used by the client in its `.split()` function as the reading component.
 pub struct Reader<R>
 where
 	R: Read,
@@ -72,18 +76,37 @@ where
 /// A Receiver that wraps a Reader and provides a default implementation using
 /// DataFrames and Messages.
 pub struct Receiver {
 	buffer: Vec<DataFrame>,
 	mask: bool,
+	// u32s instead uf usizes to economize used memory by this struct
+	max_dataframe_size: u32,
+	max_message_size: u32,
 }
 
 impl Receiver {
 	/// Create a new Receiver using the specified Reader.
+	/// 
+	/// Uses built-in limits for dataframe and message sizes. 
 	pub fn new(mask: bool) -> Receiver {
+		Receiver::new_with_limits(mask, DEFAULT_MAX_DATAFRAME_SIZE, DEFAULT_MAX_MESSAGE_SIZE)
+	}
+
+	/// Create a new Receiver using the specified Reader, with configurable limits
+	/// 
+	/// Sizes should not be larger than `u32::MAX`.
+	/// 
+	/// Note that `max_message_size` denotes message size where no new dataframes would be read,
+	/// so actual maximum message size is larger.
+	pub fn new_with_limits(mask: bool, max_dataframe_size: usize, max_message_size: usize) -> Receiver {
+		let max_dataframe_size: u32 = max_dataframe_size.min(u32::MAX as usize) as u32;
+		let max_message_size: u32 = max_message_size.min(u32::MAX as usize) as u32;
 		Receiver {
 			buffer: Vec::new(),
 			mask,
+			max_dataframe_size,
+			max_message_size,
 		}
 	}
 }
 
 impl ws::Receiver for Receiver {
@@ -94,28 +117,30 @@ impl ws::Receiver for Receiver {
 	/// Reads a single data frame from the remote endpoint.
 	fn recv_dataframe<R>(&mut self, reader: &mut R) -> WebSocketResult<DataFrame>
 	where
 		R: Read,
 	{
-		DataFrame::read_dataframe(reader, self.mask)
+		DataFrame::read_dataframe_with_limit(reader, self.mask, self.max_dataframe_size as usize)
 	}
 
 	/// Returns the data frames that constitute one message.
 	fn recv_message_dataframes<R>(&mut self, reader: &mut R) -> WebSocketResult<Vec<DataFrame>>
 	where
 		R: Read,
 	{
+		let mut current_message_length : usize = self.buffer.iter().map(|x|x.data.len()).sum();
 		let mut finished = if self.buffer.is_empty() {
 			let first = self.recv_dataframe(reader)?;
 
 			if first.opcode == Opcode::Continuation {
 				return Err(WebSocketError::ProtocolError(
 					"Unexpected continuation data frame opcode",
 				));
 			}
 
 			let finished = first.finished;
+			current_message_length += first.data.len();
 			self.buffer.push(first);
 			finished
 		} else {
 			false
 		};
@@ -124,11 +149,14 @@ impl ws::Receiver for Receiver {
 			let next = self.recv_dataframe(reader)?;
 			finished = next.finished;
 
 			match next.opcode as u8 {
 				// Continuation opcode
-				0 => self.buffer.push(next),
+				0 => {
+					current_message_length += next.data.len();
+					self.buffer.push(next)
+				}
 				// Control frame
 				8..=15 => {
 					return Ok(vec![next]);
 				}
 				// Others
@@ -136,10 +164,23 @@ impl ws::Receiver for Receiver {
 					return Err(WebSocketError::ProtocolError(
 						"Unexpected data frame opcode",
 					));
 				}
 			}
+
+			if !finished {
+				if self.buffer.len() >= MAX_DATAFRAMES_IN_ONE_MESSAGE {
+					return Err(WebSocketError::ProtocolError(
+						"Exceeded count of data frames in one WebSocket message",
+					));
+				}
+				if current_message_length >= self.max_message_size as usize {
+					return Err(WebSocketError::ProtocolError(
+						"Exceeded maximum WebSocket message size",
+					));
+				}
+			}
 		}
 
 		Ok(::std::mem::replace(&mut self.buffer, Vec::new()))
 	}
 }
diff --git a/websocket-base/src/dataframe.rs b/websocket-base/src/dataframe.rs
index c25221c..eb3d2e4 100644
--- a/websocket-base/src/dataframe.rs
+++ b/websocket-base/src/dataframe.rs
@@ -94,10 +94,29 @@ impl DataFrame {
 			return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "incomplete payload").into());
 		}
 
 		DataFrame::read_dataframe_body(header, data, should_be_masked)
 	}
+
+	/// Reads a DataFrame from a Reader, or error out if header declares exceeding limit you specify
+	pub fn read_dataframe_with_limit<R>(reader: &mut R, should_be_masked: bool, limit: usize) -> WebSocketResult<Self>
+	where
+		R: Read,
+	{
+		let header = dfh::read_header(reader)?;
+
+		if header.len > limit as u64 {
+			return Err(io::Error::new(io::ErrorKind::InvalidData, "exceeded DataFrame length limit").into());
+		}
+		let mut data: Vec<u8> = Vec::with_capacity(header.len as usize);
+		let read = reader.take(header.len).read_to_end(&mut data)?;
+		if (read as u64) < header.len {
+			return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "incomplete payload").into());
+		}
+
+		DataFrame::read_dataframe_body(header, data, should_be_masked)
+	}
 }
 
 impl DataFrameable for DataFrame {
 	#[inline(always)]
 	fn is_last(&self) -> bool {

Vulnerable code: 		DataFrame::read_dataframe(reader, self.mask)
				0 => self.buffer.push(next),
Fixed code: const DEFAULT_MAX_DATAFRAME_SIZE : usize = 1024*1024*100;
const DEFAULT_MAX_MESSAGE_SIZE : usize = 1024*1024*200;
const MAX_DATAFRAMES_IN_ONE_MESSAGE: usize = 1024*1024;

	// u32s instead uf usizes to economize used memory by this struct
	max_dataframe_size: u32,
	max_message_size: u32,
	/// 
	/// Uses built-in limits for dataframe and message sizes. 
		Receiver::new_with_limits(mask, DEFAULT_MAX_DATAFRAME_SIZE, DEFAULT_MAX_MESSAGE_SIZE)
	}

	/// Create a new Receiver using the specified Reader, with configurable limits
	/// 
	/// Sizes should not be larger than `u32::MAX`.
	/// 
	/// Note that `max_message_size` denotes message size where no new dataframes would be read,
	/// so actual maximum message size is larger.
	pub fn new_with_limits(mask: bool, max_dataframe_size: usize, max_message_size: usize) -> Receiver {
		let max_dataframe_size: u32 = max_dataframe_size.min(u32::MAX as usize) as u32;
		let max_message_size: u32 = max_message_size.min(u32::MAX as usize) as u32;
			max_dataframe_size,
			max_message_size,
		DataFrame::read_dataframe_with_limit(reader, self.mask, self.max_dataframe_size as usize)
		let mut current_message_length : usize = self.buffer.iter().map(|x|x.data.len()).sum();
			current_message_length += first.data.len();
				0 => {
					current_message_length += next.data.len();
					self.buffer.push(next)
				}

			if !finished {
				if self.buffer.len() >= MAX_DATAFRAMES_IN_ONE_MESSAGE {
					return Err(WebSocketError::ProtocolError(
						"Exceeded count of data frames in one WebSocket message",
					));
				}
				if current_message_length >= self.max_message_size as usize {
					return Err(WebSocketError::ProtocolError(
						"Exceeded maximum WebSocket message size",
					));
				}
			}

	/// Reads a DataFrame from a Reader, or error out if header declares exceeding limit you specify
	pub fn read_dataframe_with_limit<R>(reader: &mut R, should_be_masked: bool, limit: usize) -> WebSocketResult<Self>
	where
		R: Read,
	{
		let header = dfh::read_header(reader)?;

		if header.len > limit as u64 {
			return Err(io::Error::new(io::ErrorKind::InvalidData, "exceeded DataFrame length limit").into());
		}
		let mut data: Vec<u8> = Vec::with_capacity(header.len as usize);
		let read = reader.take(header.len).read_to_end(&mut data)?;
		if (read as u64) < header.len {
			return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "incomplete payload").into());
		}

		DataFrame::read_dataframe_body(header, data, should_be_masked)
	}