websocket: use max window bits of 15

Ticket: 7285

As this is the default for websocket, which is bigger than the
defaut for zlib usage

Also limit the decompressed content to the max-payload-size
configuration parameter also used for non-compressed content.

And also use a stateful decoder to store/remember the compression
state to be able to decompress later messages.
pull/13044/head
Philippe Antoine 3 months ago committed by Victor Julien
parent 44c8632284
commit 16f74c68aa

@ -26,19 +26,21 @@ use crate::frames::Frame;
use nom7 as nom; use nom7 as nom;
use nom7::Needed; use nom7::Needed;
use flate2::read::DeflateDecoder; use flate2::Decompress;
use flate2::FlushDecompress;
use suricata_sys::sys::AppProto; use suricata_sys::sys::AppProto;
use std; use std;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ffi::CString; use std::ffi::CString;
use std::io::Read;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
pub(super) static mut ALPROTO_WEBSOCKET: AppProto = ALPROTO_UNKNOWN; pub(super) static mut ALPROTO_WEBSOCKET: AppProto = ALPROTO_UNKNOWN;
static mut WEBSOCKET_MAX_PAYLOAD_SIZE: u32 = 0xFFFF; static mut WEBSOCKET_MAX_PAYLOAD_SIZE: u32 = 0xFFFF;
const WEBSOCKET_DECOMPRESS_BUF_SIZE: usize = 8192;
#[derive(AppLayerFrameType)] #[derive(AppLayerFrameType)]
pub enum WebSocketFrameType { pub enum WebSocketFrameType {
Header, Header,
@ -86,6 +88,9 @@ pub struct WebSocketState {
tx_id: u64, tx_id: u64,
transactions: VecDeque<WebSocketTransaction>, transactions: VecDeque<WebSocketTransaction>,
c2s_dec: Option<flate2::Decompress>,
s2c_dec: Option<flate2::Decompress>,
c2s_buf: WebSocketReassemblyBuffer, c2s_buf: WebSocketReassemblyBuffer,
s2c_buf: WebSocketReassemblyBuffer, s2c_buf: WebSocketReassemblyBuffer,
@ -195,10 +200,19 @@ impl WebSocketState {
} }
tx.tx_data.set_event(WebSocketEvent::SkipEndOfPayload as u8); tx.tx_data.set_event(WebSocketEvent::SkipEndOfPayload as u8);
} }
let buf = if direction == Direction::ToClient { if pdu.compress {
&mut self.s2c_buf // 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 { } else {
&mut self.c2s_buf (&mut self.c2s_buf, &mut self.c2s_dec)
}; };
let mut compress = pdu.compress; let mut compress = pdu.compress;
if !buf.data.is_empty() || !pdu.fin { if !buf.data.is_empty() || !pdu.fin {
@ -225,13 +239,42 @@ impl WebSocketState {
buf.compress = false; buf.compress = false;
// cf RFC 7692 section-7.2.2 // cf RFC 7692 section-7.2.2
tx.pdu.payload.extend_from_slice(&[0, 0, 0xFF, 0xFF]); tx.pdu.payload.extend_from_slice(&[0, 0, 0xFF, 0xFF]);
let mut deflater = DeflateDecoder::new(&tx.pdu.payload[..]); let mut v = Vec::with_capacity(WEBSOCKET_DECOMPRESS_BUF_SIZE);
let mut v = Vec::new(); if let Some(dec) = dec {
// do not check result because let expect = dec.total_in() + tx.pdu.payload.len() as u64;
// deflate with rust backend fails on good input cf https://github.com/rust-lang/flate2-rs/issues/389 let start = dec.total_in();
let _ = deflater.read_to_end(&mut v); let mut e = dec.decompress_vec(
if !v.is_empty() { &tx.pdu.payload,
std::mem::swap(&mut tx.pdu.payload, &mut v); &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); self.transactions.push_back(tx);

@ -970,6 +970,7 @@ app-layer:
websocket: websocket:
#enabled: yes #enabled: yes
# Maximum used payload size, the rest is skipped # Maximum used payload size, the rest is skipped
# Also applies as a maximum for uncompressed data
# max-payload-size: 64 KiB # max-payload-size: 64 KiB
rdp: rdp:
#enabled: yes #enabled: yes

Loading…
Cancel
Save