diff --git a/rust/src/websocket/websocket.rs b/rust/src/websocket/websocket.rs index 59e067fb9d..3b86defe9c 100644 --- a/rust/src/websocket/websocket.rs +++ b/rust/src/websocket/websocket.rs @@ -26,19 +26,21 @@ use crate::frames::Frame; use nom7 as nom; use nom7::Needed; -use flate2::read::DeflateDecoder; +use flate2::Decompress; +use flate2::FlushDecompress; use suricata_sys::sys::AppProto; use std; use std::collections::VecDeque; use std::ffi::CString; -use std::io::Read; use std::os::raw::{c_char, c_int, c_void}; pub(super) static mut ALPROTO_WEBSOCKET: AppProto = ALPROTO_UNKNOWN; static mut WEBSOCKET_MAX_PAYLOAD_SIZE: u32 = 0xFFFF; +const WEBSOCKET_DECOMPRESS_BUF_SIZE: usize = 8192; + #[derive(AppLayerFrameType)] pub enum WebSocketFrameType { Header, @@ -86,6 +88,9 @@ pub struct WebSocketState { tx_id: u64, transactions: VecDeque, + c2s_dec: Option, + s2c_dec: Option, + c2s_buf: WebSocketReassemblyBuffer, s2c_buf: WebSocketReassemblyBuffer, @@ -195,10 +200,19 @@ impl WebSocketState { } tx.tx_data.set_event(WebSocketEvent::SkipEndOfPayload as u8); } - let buf = if direction == Direction::ToClient { - &mut self.s2c_buf + if pdu.compress { + // RFC 7692 section 7.1.2 states that + // absence of precision means LZ77 sliding window of up to 2^15 bytes + if direction == Direction::ToClient && self.s2c_dec.is_none() { + self.s2c_dec = Some(Decompress::new_with_window_bits(false, 15)); + } else if direction == Direction::ToServer && self.c2s_dec.is_none() { + self.c2s_dec = Some(Decompress::new_with_window_bits(false, 15)); + } + } + let (buf, dec) = if direction == Direction::ToClient { + (&mut self.s2c_buf, &mut self.s2c_dec) } else { - &mut self.c2s_buf + (&mut self.c2s_buf, &mut self.c2s_dec) }; let mut compress = pdu.compress; if !buf.data.is_empty() || !pdu.fin { @@ -225,13 +239,42 @@ impl WebSocketState { buf.compress = false; // cf RFC 7692 section-7.2.2 tx.pdu.payload.extend_from_slice(&[0, 0, 0xFF, 0xFF]); - let mut deflater = DeflateDecoder::new(&tx.pdu.payload[..]); - let mut v = Vec::new(); - // do not check result because - // deflate with rust backend fails on good input cf https://github.com/rust-lang/flate2-rs/issues/389 - let _ = deflater.read_to_end(&mut v); - if !v.is_empty() { - std::mem::swap(&mut tx.pdu.payload, &mut v); + let mut v = Vec::with_capacity(WEBSOCKET_DECOMPRESS_BUF_SIZE); + if let Some(dec) = dec { + let expect = dec.total_in() + tx.pdu.payload.len() as u64; + let start = dec.total_in(); + let mut e = dec.decompress_vec( + &tx.pdu.payload, + &mut v, + FlushDecompress::Finish, + ); + while e.is_ok() && dec.total_in() < expect { + let mut s = vec![0u8; WEBSOCKET_DECOMPRESS_BUF_SIZE]; + let before = dec.total_out(); + let check = dec.total_in(); + e = dec.decompress( + &tx.pdu.payload[(dec.total_in() - start) as usize..], + &mut s, + FlushDecompress::Finish, + ); + if v.len() < max_pl_size as usize { + let end = if v.len() + (dec.total_out() - before) as usize + > max_pl_size as usize + { + v.len() - max_pl_size as usize + } else { + (dec.total_out() - before) as usize + }; + v.extend_from_slice(&s[..end]); + } + if check >= dec.total_in() { + // safety check against infinite loop : dec.total_in() should increase + break; + } + } + if !v.is_empty() { + std::mem::swap(&mut tx.pdu.payload, &mut v); + } } } self.transactions.push_back(tx); diff --git a/suricata.yaml.in b/suricata.yaml.in index 93e2671eb1..7794cf348f 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -970,6 +970,7 @@ app-layer: websocket: #enabled: yes # Maximum used payload size, the rest is skipped + # Also applies as a maximum for uncompressed data # max-payload-size: 64 KiB rdp: #enabled: yes