]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
smb2: allow limiting in-flight data size/cnt
authorVictor Julien <vjulien@oisf.net>
Sat, 16 Apr 2022 04:58:20 +0000 (06:58 +0200)
committerVictor Julien <vjulien@oisf.net>
Wed, 20 Apr 2022 18:48:10 +0000 (20:48 +0200)
Allow limiting in-flight out or order data chunks per size or count.

Implemented for read and writes separately:

app-layer.protocols.smb.max-write-queue-size
app-layer.protocols.smb.max-write-queue-cnt
app-layer.protocols.smb.max-read-queue-size
app-layer.protocols.smb.max-read-queue-cnt

Backport note: Modified to support Rust 1.33 -- Jason Ish

(cherry picked from commit 4be8334c9e95cc520c9c6a63a98d6b160915d07f)

rust/src/smb/events.rs
rust/src/smb/smb.rs
rust/src/smb/smb2.rs
src/app-layer-smb.c

index 8bd7fe185ef59368572134fc036a203e6b4ab1c8..604804f285eed570f687278230e8f0ce1e0cceb0 100644 (file)
@@ -34,8 +34,12 @@ pub enum SMBEvent {
     ReadRequestTooLarge = 10,
     /// READ response bigger than `max_read_size`
     ReadResponseTooLarge = 11,
+    ReadResponseQueueSizeExceeded = 12,
+    ReadResponseQueueCntExceeded = 13,
     /// WRITE request for more than `max_write_size`
-    WriteRequestTooLarge = 12,
+    WriteRequestTooLarge = 14,
+    WriteQueueSizeExceeded = 15,
+    WriteQueueCntExceeded = 16,
 }
 
 impl SMBEvent {
@@ -53,7 +57,11 @@ impl SMBEvent {
             9 => Some(SMBEvent::ResponseToServer),
             10 => Some(SMBEvent::ReadRequestTooLarge),
             11 => Some(SMBEvent::ReadResponseTooLarge),
-            12 => Some(SMBEvent::WriteRequestTooLarge),
+            12 => Some(SMBEvent::ReadResponseQueueSizeExceeded),
+            13 => Some(SMBEvent::ReadResponseQueueCntExceeded),
+            14 => Some(SMBEvent::WriteRequestTooLarge),
+            15 => Some(SMBEvent::WriteQueueSizeExceeded),
+            16 => Some(SMBEvent::WriteQueueCntExceeded),
             _ => None,
         }
     }
@@ -62,19 +70,23 @@ impl SMBEvent {
 pub fn smb_str_to_event(instr: &str) -> i32 {
     SCLogDebug!("checking {}", instr);
     match instr {
-        "internal_error"                => SMBEvent::InternalError as i32,
-        "malformed_data"                => SMBEvent::MalformedData as i32,
-        "record_overflow"               => SMBEvent::RecordOverflow as i32,
-        "malformed_ntlmssp_request"     => SMBEvent::MalformedNtlmsspRequest as i32,
-        "malformed_ntlmssp_response"    => SMBEvent::MalformedNtlmsspResponse as i32,
-        "duplicate_negotiate"           => SMBEvent::DuplicateNegotiate as i32,
-        "negotiate_malformed_dialects"  => SMBEvent::NegotiateMalformedDialects as i32,
-        "file_overlap"                  => SMBEvent::FileOverlap as i32,
-        "request_to_client"             => SMBEvent::RequestToClient as i32,
-        "response_to_server"            => SMBEvent::ResponseToServer as i32,
-        "read_request_too_large"        => SMBEvent::ReadRequestTooLarge as i32,
-        "read_response_too_large"       => SMBEvent::ReadResponseTooLarge as i32,
-        "write_request_too_large"       => SMBEvent::WriteRequestTooLarge as i32,
+        "internal_error"                    => SMBEvent::InternalError as i32,
+        "malformed_data"                    => SMBEvent::MalformedData as i32,
+        "record_overflow"                   => SMBEvent::RecordOverflow as i32,
+        "malformed_ntlmssp_request"         => SMBEvent::MalformedNtlmsspRequest as i32,
+        "malformed_ntlmssp_response"        => SMBEvent::MalformedNtlmsspResponse as i32,
+        "duplicate_negotiate"               => SMBEvent::DuplicateNegotiate as i32,
+        "negotiate_malformed_dialects"      => SMBEvent::NegotiateMalformedDialects as i32,
+        "file_overlap"                      => SMBEvent::FileOverlap as i32,
+        "request_to_client"                 => SMBEvent::RequestToClient as i32,
+        "response_to_server"                => SMBEvent::ResponseToServer as i32,
+        "read_request_too_large"            => SMBEvent::ReadRequestTooLarge as i32,
+        "read_response_too_large"           => SMBEvent::ReadResponseTooLarge as i32,
+        "read_queue_size_too_large"         => SMBEvent::ReadResponseQueueSizeExceeded as i32,
+        "read_queue_cnt_too_large"          => SMBEvent::ReadResponseQueueCntExceeded as i32,
+        "write_request_too_large"           => SMBEvent::WriteRequestTooLarge as i32,
+        "write_queue_size_too_large"        => SMBEvent::WriteQueueSizeExceeded as i32,
+        "write_queue_cnt_too_large"         => SMBEvent::WriteQueueCntExceeded as i32,
         _ => -1,
     }
 }
index 8bbc8f355884ce1ca73b96582bd44c60769c1ef8..3bb2155742752b326842ca57a07111bd69f810ee 100644 (file)
@@ -52,7 +52,11 @@ use crate::smb::files::*;
 use crate::smb::smb2_ioctl::*;
 
 pub static mut SMB_CFG_MAX_READ_SIZE: u32 = 0;
+pub static mut SMB_CFG_MAX_READ_QUEUE_SIZE: u32 = 0;
+pub static mut SMB_CFG_MAX_READ_QUEUE_CNT: u32 = 0;
 pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 0;
+pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 0;
+pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 0;
 
 pub static mut SURICATA_SMB_FILE_CONFIG: Option<&'static SuricataFileContext> = None;
 
@@ -2229,7 +2233,11 @@ pub extern "C" fn rs_smb_state_get_event_info_by_id(event_id: std::os::raw::c_in
             SMBEvent::ResponseToServer => { "response_to_server\0" },
             SMBEvent::ReadRequestTooLarge => { "read_request_too_large\0" },
             SMBEvent::ReadResponseTooLarge => { "read_response_too_large\0" },
+            SMBEvent::ReadResponseQueueSizeExceeded => { "read_queue_size_too_large\0" },
+            SMBEvent::ReadResponseQueueCntExceeded => { "read_queue_cnt_too_large\0" },
             SMBEvent::WriteRequestTooLarge => { "write_request_too_large\0" },
+            SMBEvent::WriteQueueSizeExceeded => { "write_queue_size_too_large\0" },
+            SMBEvent::WriteQueueCntExceeded => { "write_queue_cnt_too_large\0" },
         };
         unsafe{
             *event_name = estr.as_ptr() as *const std::os::raw::c_char;
@@ -2268,8 +2276,13 @@ pub extern "C" fn rs_smb_state_get_event_info(event_name: *const std::os::raw::c
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn rs_smb_set_conf_val(max_read_size: u32, max_write_size: u32)
+pub unsafe extern "C" fn rs_smb_set_conf_val(max_read_size: u32, max_write_size: u32,
+    max_write_queue_size: u32, max_write_queue_cnt: u32, max_read_queue_size: u32, max_read_queue_cnt: u32)
 {
     SMB_CFG_MAX_READ_SIZE = max_read_size;
     SMB_CFG_MAX_WRITE_SIZE = max_write_size;
+    SMB_CFG_MAX_WRITE_QUEUE_SIZE = max_write_queue_size;
+    SMB_CFG_MAX_WRITE_QUEUE_CNT = max_write_queue_cnt;
+    SMB_CFG_MAX_READ_QUEUE_SIZE = max_read_queue_size;
+    SMB_CFG_MAX_READ_QUEUE_CNT = max_read_queue_cnt;
 }
index 216a8681fa271e8b6c0cc2ef5deebd4b04e6b0f4..08e65ddeb939a89f097e6224ad73b0ca43ce2472 100644 (file)
@@ -114,6 +114,9 @@ fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b
 
 pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
 {
+    let max_queue_size = unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE };
+    let max_queue_cnt = unsafe { SMB_CFG_MAX_READ_QUEUE_CNT };
+
     smb2_read_response_record_generic(state, r);
 
     match parse_smb2_response_read(r.data) {
@@ -153,21 +156,37 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
 
             let mut set_event_fileoverlap = false;
             // look up existing tracker and if we have it update it
-            let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) {
-                Some((tx, files, flags)) => {
-                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
-                        let file_id : u32 = tx.id as u32;
-                        if offset < tdf.file_tracker.tracked {
-                            set_event_fileoverlap = true;
-                        }
+
+            let mut found = false;
+            let mut skip = None;
+            let mut event= None;
+            if let Some((tx, files, flags)) = state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) {
+                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                    let file_id : u32 = tx.id as u32;
+                    if offset < tdf.file_tracker.tracked {
+                        set_event_fileoverlap = true;
+                    }
+                    if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() {
+                        event = Some(SMBEvent::ReadResponseQueueCntExceeded);
+                        skip = Some((rd.len, rd.data.len()));
+                    } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize {
+                        event = Some(SMBEvent::ReadResponseQueueCntExceeded);
+                        skip = Some((rd.len, rd.data.len()));
+                    } else {
                         filetracker_newchunk(&mut tdf.file_tracker, files, flags,
                                 &tdf.file_name, rd.data, offset,
                                 rd.len, false, &file_id);
                     }
-                    true
-                },
-                None => { false },
-            };
+                }
+                found = true;
+            }
+            if let Some((rd_len, rd_data_len)) = skip {
+                state.set_skip(STREAM_TOCLIENT, rd_len, rd_data_len as u32);
+            }
+            if let Some(event) = event {
+                state.set_event(event);
+            }
+
             SCLogDebug!("existing file tx? {}", found);
             if !found {
                 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
@@ -203,6 +222,8 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
                     }
                 }
 
+                let mut event = None;
+                let mut skip = None;
                 if is_pipe && is_dcerpc {
                     SCLogDebug!("SMBv2 DCERPC read");
                     let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
@@ -210,26 +231,42 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
                     smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data);
                 } else if is_pipe {
                     SCLogDebug!("non-DCERPC pipe");
-                    state.set_skip(STREAM_TOCLIENT, rd.len, rd.data.len() as u32);
+                    skip = Some((rd.len, rd.data.len()));
                 } else {
                     let file_name = match state.guid2name_map.get(&file_guid) {
-                        Some(n) => { n.to_vec() },
-                        None => { b"<unknown>".to_vec() },
+                        Some(n) => { n.to_vec() }
+                        None => { b"<unknown>".to_vec() }
                     };
                     let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOCLIENT);
+
+                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ);
+                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
+
                     if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                        tdf.share_name = share_name;
                         let file_id : u32 = tx.id as u32;
                         if offset < tdf.file_tracker.tracked {
                             set_event_fileoverlap = true;
                         }
-                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
-                                &file_name, rd.data, offset,
-                                rd.len, false, &file_id);
-                        tdf.share_name = share_name;
+                        if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() {
+                            event = Some(SMBEvent::ReadResponseQueueSizeExceeded);
+                            skip = Some((rd.len, rd.data.len()));
+                        } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize {
+                            event = Some(SMBEvent::ReadResponseQueueCntExceeded);
+                            skip = Some((rd.len, rd.data.len()));
+                        } else {
+                            filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                    &file_name, rd.data, offset,
+                                    rd.len, false, &file_id);
+                        }
                     }
-                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ);
-                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
-                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
+                }
+                if let Some(event) = event {
+                    state.set_event(event);
+                }
+                if let Some((rd_len, rd_data_len)) = skip {
+                    state.set_skip(STREAM_TOCLIENT, rd_len, rd_data_len as u32);
                 }
             }
 
@@ -247,6 +284,9 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
 
 pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
 {
+    let max_queue_size = unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE };
+    let max_queue_cnt = unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT };
+
     SCLogDebug!("SMBv2/WRITE: request record");
     if smb2_create_new_tx(r.command) {
         let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
@@ -273,21 +313,36 @@ pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
             };
 
             let mut set_event_fileoverlap = false;
-            let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOSERVER) {
-                Some((tx, files, flags)) => {
-                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
-                        let file_id : u32 = tx.id as u32;
-                        if wr.wr_offset < tdf.file_tracker.tracked {
-                            set_event_fileoverlap = true;
-                        }
+            let mut found = false;
+            let mut event = None;
+            let mut skip = None;
+            if let Some((tx, files, flags)) = state.get_file_tx_by_fuid(&file_guid, STREAM_TOSERVER) {
+                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+                    let file_id : u32 = tx.id as u32;
+                    if wr.wr_offset < tdf.file_tracker.tracked {
+                        set_event_fileoverlap = true;
+                    }
+                    if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() {
+                        event = Some(SMBEvent::WriteQueueSizeExceeded);
+                        skip = Some((wr.wr_len, wr.data.len()));
+                    } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize {
+                        event = Some(SMBEvent::WriteQueueSizeExceeded);
+                        skip = Some((wr.wr_len, wr.data.len()));
+                    } else {
                         filetracker_newchunk(&mut tdf.file_tracker, files, flags,
                                 &file_name, wr.data, wr.wr_offset,
                                 wr.wr_len, false, &file_id);
                     }
-                    true
-                },
-                None => { false },
-            };
+                }
+                found = true;
+            }
+            if let Some(event) = event {
+                state.set_event(event);
+            }
+            if let Some((len, data_len)) = skip {
+                state.set_skip(STREAM_TOSERVER, len, data_len as u32);
+            }
+
             if !found {
                 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
                 let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) {
@@ -323,6 +378,8 @@ pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
                         SCLogDebug!("SMBv2/WRITE: not DCERPC");
                     }
                 }
+                let mut event = None;
+                let mut skip = None;
                 if is_pipe && is_dcerpc {
                     SCLogDebug!("SMBv2 DCERPC write");
                     let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
@@ -333,18 +390,33 @@ pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
                     state.set_skip(STREAM_TOSERVER, wr.wr_len, wr.data.len() as u32);
                 } else {
                     let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOSERVER);
+                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE);
+                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
+                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
                     if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
                         let file_id : u32 = tx.id as u32;
                         if wr.wr_offset < tdf.file_tracker.tracked {
                             set_event_fileoverlap = true;
                         }
-                        filetracker_newchunk(&mut tdf.file_tracker, files, flags,
-                                &file_name, wr.data, wr.wr_offset,
-                                wr.wr_len, false, &file_id);
+
+                        if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() {
+                            event = Some(SMBEvent::WriteQueueSizeExceeded);
+                            skip = Some((wr.wr_len, wr.data.len() as u32));
+                        } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize {
+                            event = Some(SMBEvent::WriteQueueSizeExceeded);
+                            skip = Some((wr.wr_len, wr.data.len() as u32));
+                        } else {
+                            filetracker_newchunk(&mut tdf.file_tracker, files, flags,
+                                    &file_name, wr.data, wr.wr_offset,
+                                    wr.wr_len, false, &file_id);
+                        }
                     }
-                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE);
-                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
-                            r.session_id, r.tree_id, 0); // TODO move into new_file_tx
+                }
+                if let Some(event) = event {
+                    state.set_event(event);
+                }
+                if let Some((len, data_len)) = skip {
+                    state.set_skip(STREAM_TOSERVER, len, data_len as u32);
                 }
             }
 
index 8db224a874ea1d9ced1349476ec36f308910aa5e..54880f5f84042e19f2d882a86ee7962b2e7d21bc 100644 (file)
@@ -263,6 +263,10 @@ void RegisterSMBParsers(void)
     const char *proto_name = "smb";
     uint32_t max_read_size = 0;
     uint32_t max_write_size = 0;
+    uint32_t max_write_queue_size = 0;
+    uint32_t max_write_queue_cnt = 0;
+    uint32_t max_read_queue_size = 0;
+    uint32_t max_read_queue_cnt = 0;
     /** SMB */
     if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
         AppLayerProtoDetectRegisterProtocol(ALPROTO_SMB, proto_name);
@@ -367,7 +371,53 @@ void RegisterSMBParsers(void)
         }
         SCLogConfig("SMB max-write-size: %u", max_write_size);
 
-        rs_smb_set_conf_val(max_read_size, max_write_size);
+        ConfNode *wqs = ConfGetNode("app-layer.protocols.smb.max-write-queue-size");
+        if (wqs != NULL) {
+            uint32_t value;
+            if (ParseSizeStringU32(wqs->val, &value) < 0) {
+                SCLogError(
+                        SC_ERR_SMB_CONFIG, "invalid value for max-write-queue-size %s", wqs->val);
+            } else {
+                max_write_queue_size = value;
+            }
+        }
+        SCLogConfig("SMB max-write-queue-size: %u", max_write_queue_size);
+
+        ConfNode *wqc = ConfGetNode("app-layer.protocols.smb.max-write-queue-cnt");
+        if (wqc != NULL) {
+            uint32_t value;
+            if (ParseSizeStringU32(wqc->val, &value) < 0) {
+                SCLogError(SC_ERR_SMB_CONFIG, "invalid value for max-write-queue-cnt %s", wqc->val);
+            } else {
+                max_write_queue_cnt = value;
+            }
+        }
+        SCLogConfig("SMB max-write-queue-cnt: %u", max_write_queue_cnt);
+
+        ConfNode *rqs = ConfGetNode("app-layer.protocols.smb.max-read-queue-size");
+        if (rqs != NULL) {
+            uint32_t value;
+            if (ParseSizeStringU32(rqs->val, &value) < 0) {
+                SCLogError(SC_ERR_SMB_CONFIG, "invalid value for max-read-queue-size %s", rqs->val);
+            } else {
+                max_read_queue_size = value;
+            }
+        }
+        SCLogConfig("SMB max-read-queue-size: %u", max_read_queue_size);
+
+        ConfNode *rqc = ConfGetNode("app-layer.protocols.smb.max-read-queue-cnt");
+        if (rqc != NULL) {
+            uint32_t value;
+            if (ParseSizeStringU32(rqc->val, &value) < 0) {
+                SCLogError(SC_ERR_SMB_CONFIG, "invalid value for max-read-queue-cnt %s", rqc->val);
+            } else {
+                max_read_queue_cnt = value;
+            }
+        }
+        SCLogConfig("SMB max-read-queue-cnt: %u", max_read_queue_cnt);
+
+        rs_smb_set_conf_val(max_read_size, max_write_size, max_write_queue_size,
+                max_write_queue_cnt, max_read_queue_size, max_read_queue_cnt);
     } else {
         SCLogConfig("Parsed disabled for %s protocol. Protocol detection"
                   "still on.", proto_name);