]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
websocket: use max window bits of 15
authorPhilippe Antoine <pantoine@oisf.net>
Thu, 3 Apr 2025 08:49:38 +0000 (10:49 +0200)
committerVictor Julien <victor@inliniac.net>
Fri, 18 Apr 2025 10:52:19 +0000 (12:52 +0200)
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.

rust/src/websocket/websocket.rs
suricata.yaml.in

index 59e067fb9dbe80d51838c984457a6f6fda09f213..3b86defe9cac38d7cfaa8f8904ce46161524671b 100644 (file)
@@ -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<WebSocketTransaction>,
 
+    c2s_dec: Option<flate2::Decompress>,
+    s2c_dec: Option<flate2::Decompress>,
+
     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);
index 93e2671eb151b0165ca6e505e1df7b080f961f65..7794cf348f7ba1f46c09fcfc62243546818d2fe2 100644 (file)
@@ -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