From 96b72b18aa84e6e2df667f9ee43c56db8a3c78c0 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 16 Apr 2022 06:58:20 +0200 Subject: [PATCH] smb2: allow limiting in-flight data size/cnt 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 | 42 +++++++----- rust/src/smb/smb.rs | 15 ++++- rust/src/smb/smb2.rs | 148 ++++++++++++++++++++++++++++++----------- src/app-layer-smb.c | 52 ++++++++++++++- 4 files changed, 202 insertions(+), 55 deletions(-) diff --git a/rust/src/smb/events.rs b/rust/src/smb/events.rs index 8bd7fe185e..604804f285 100644 --- a/rust/src/smb/events.rs +++ b/rust/src/smb/events.rs @@ -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, } } diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs index 8bbc8f3558..3bb2155742 100644 --- a/rust/src/smb/smb.rs +++ b/rust/src/smb/smb.rs @@ -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; } diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs index 216a8681fa..08e65ddeb9 100644 --- a/rust/src/smb/smb2.rs +++ b/rust/src/smb/smb2.rs @@ -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"".to_vec() }, + Some(n) => { n.to_vec() } + None => { b"".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); } } diff --git a/src/app-layer-smb.c b/src/app-layer-smb.c index 8db224a874..54880f5f84 100644 --- a/src/app-layer-smb.c +++ b/src/app-layer-smb.c @@ -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); -- 2.47.2