diff --git a/rust/src/smb/files.rs b/rust/src/smb/files.rs index 0ac41975b0..8f36996569 100644 --- a/rust/src/smb/files.rs +++ b/rust/src/smb/files.rs @@ -19,6 +19,7 @@ use crate::applayer::AppLayerGetFileStateRust; use crate::core::*; use crate::direction::Direction; use crate::filetracker::*; +use crate::smb::events::SMBEvent; use std; use crate::smb::smb::*; @@ -246,6 +247,39 @@ impl SMBState { } } +/// Event to raise if enqueuing `len` bytes at `offset` would exceed the +/// configured SMB queue limits. OOO data at a new offset grows the queue; +/// in-order appends drain it and are never rejected. +pub(crate) fn smb_queue_limit_event( + ft: &FileTransferTracker, offset: u64, len: u64, to_client: bool, +) -> Option { + let (max_size, max_cnt, ev_size, ev_cnt) = if to_client { + ( + unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE }, + unsafe { SMB_CFG_MAX_READ_QUEUE_CNT }, + SMBEvent::ReadQueueSizeExceeded, + SMBEvent::ReadQueueCntExceeded, + ) + } else { + ( + unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE }, + unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT }, + SMBEvent::WriteQueueSizeExceeded, + SMBEvent::WriteQueueCntExceeded, + ) + }; + if max_size != 0 + && ft.is_ooo_offset(offset) + && ft.get_inflight_size() + len > u64::from(max_size) + { + return Some(ev_size); + } + if max_cnt != 0 && ft.new_chunk_entry(offset) && ft.get_inflight_cnt() + 1 > max_cnt as usize { + return Some(ev_cnt); + } + None +} + use crate::applayer::AppLayerGetFileState; pub(super) unsafe extern "C" fn smb_gettxfiles( diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs index 38b3eaef4f..f5881d19ee 100644 --- a/rust/src/smb/smb1.rs +++ b/rust/src/smb/smb1.rs @@ -1078,16 +1078,29 @@ pub fn smb1_write_request_record( if rd.offset < tdf.file_tracker.tracked { set_event_fileoverlap = true; } - filetracker_newchunk( - &mut tdf.file_tracker, - &file_name, - rd.data, + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, rd.offset, - rd.len, + rd.len as u64, false, - &file_id, - ); - SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx); + ) { + // Attach to the affected file tx (not the newest tx). + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + filetracker_newchunk( + &mut tdf.file_tracker, + &file_name, + rd.data, + rd.offset, + rd.len, + false, + &file_id, + ); + } + SCLogDebug!("FID {:?} found at tx {}", file_fid, file_id); } true } @@ -1109,25 +1122,44 @@ pub fn smb1_write_request_record( else { return; }; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX); + SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx); if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { let file_id: u32 = tx.id as u32; if rd.offset < tdf.file_tracker.tracked { set_event_fileoverlap = true; } - filetracker_newchunk( - &mut tdf.file_tracker, - &file_name, - rd.data, + tdf.share_name = share_name; + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, rd.offset, - rd.len, + rd.len as u64, false, - &file_id, - ); - tdf.share_name = share_name; - SCLogDebug!("tdf {:?}", tdf); + ) { + // tx just created: set the event on it directly. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + // Chunk rejected before the file was opened: complete + close the + // tx and zero files_opened, else it dangles until teardown. + tx.request_done = true; + tx.response_done = true; + tx.tx_data.0.files_opened = 0; + state.set_skip(Direction::ToServer, nbss_remaining); + // The trailing set_file_left also claims these bytes: safe -- records + // only reach here with no chunk armed, and handle_skip runs first. + } else { + filetracker_newchunk( + &mut tdf.file_tracker, + &file_name, + rd.data, + rd.offset, + rd.len, + false, + &file_id, + ); + } } - tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX); - SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx); } } if set_event_fileoverlap { @@ -1206,15 +1238,28 @@ pub fn smb1_read_response_record( if offset < tdf.file_tracker.tracked { set_event_fileoverlap = true; } - filetracker_newchunk( - &mut tdf.file_tracker, - &file_name, - rd.data, + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, offset, - rd.len, - false, - &file_id, - ); + rd.len as u64, + true, + ) { + // Attach to the affected file tx (not the newest tx). + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk( + &mut tdf.file_tracker, + &file_name, + rd.data, + offset, + rd.len, + false, + &file_id, + ); + } } true } @@ -1226,24 +1271,44 @@ pub fn smb1_read_response_record( else { return; }; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX); + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { let file_id: u32 = tx.id as u32; - SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); if offset < tdf.file_tracker.tracked { set_event_fileoverlap = true; } - filetracker_newchunk( - &mut tdf.file_tracker, - &file_name, - rd.data, - offset, - rd.len, - false, - &file_id, - ); tdf.share_name = share_name; + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, + offset, + rd.len as u64, + true, + ) { + // tx just created: set the event on it directly. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + // Chunk rejected before the file was opened: complete + close the + // tx and zero files_opened, else it dangles until teardown. + tx.request_done = true; + tx.response_done = true; + tx.tx_data.0.files_opened = 0; + // The handler tail below also arms set_file_left for these bytes: + // safe -- reached only with no chunk armed, handle_skip first. + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk( + &mut tdf.file_tracker, + &file_name, + rd.data, + offset, + rd.len, + false, + &file_id, + ); + } } - tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX); } if set_event_fileoverlap { state.set_event(SMBEvent::FileOverlap); diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs index ba72231360..23a5439bfe 100644 --- a/rust/src/smb/smb2.rs +++ b/rust/src/smb/smb2.rs @@ -116,9 +116,6 @@ fn smb2_read_response_record_generic(state: &mut SMBState, r: &Smb2Record) { } pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) { - 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) { @@ -172,16 +169,13 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema 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 - > u64::from(max_queue_size) - { - state.set_event(SMBEvent::ReadQueueSizeExceeded); - state.set_skip(Direction::ToClient, nbss_remaining); - } else if max_queue_cnt != 0 - && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize + if let Some(ev) = + smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true) { - state.set_event(SMBEvent::ReadQueueCntExceeded); + // Attach to the affected file tx, not the newest tx. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; state.set_skip(Direction::ToClient, nbss_remaining); } else { filetracker_newchunk( @@ -264,17 +258,21 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema 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 - > u64::from(max_queue_size) - { - state.set_event(SMBEvent::ReadQueueSizeExceeded); - state.set_skip(Direction::ToClient, nbss_remaining); - } else if max_queue_cnt != 0 - && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize + if let Some(ev) = + smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true) { - state.set_event(SMBEvent::ReadQueueCntExceeded); + // tx just created: set the event on it directly. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + // Chunk rejected before the file was opened: complete + close the + // tx and zero files_opened, else it dangles until teardown. + tx.request_done = true; + tx.response_done = true; + tx.tx_data.0.files_opened = 0; state.set_skip(Direction::ToClient, nbss_remaining); + // The trailing set_file_left also claims these bytes: safe -- records + // only reach here with no chunk armed, and handle_skip runs first. } else { filetracker_newchunk( &mut tdf.file_tracker, @@ -308,9 +306,6 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema } pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) { - 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); @@ -355,16 +350,16 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema 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 - > u64::from(max_queue_size) - { - state.set_event(SMBEvent::WriteQueueSizeExceeded); - state.set_skip(Direction::ToServer, nbss_remaining); - } else if max_queue_cnt != 0 - && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize - { - state.set_event(SMBEvent::WriteQueueCntExceeded); + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, + wr.wr_offset, + wr.wr_len as u64, + false, + ) { + // Attach to the affected file tx, not the newest tx. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; state.set_skip(Direction::ToServer, nbss_remaining); } else { filetracker_newchunk( @@ -443,17 +438,24 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema set_event_fileoverlap = true; } - if max_queue_size != 0 - && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 - > u64::from(max_queue_size) - { - state.set_event(SMBEvent::WriteQueueSizeExceeded); - state.set_skip(Direction::ToServer, nbss_remaining); - } else if max_queue_cnt != 0 - && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize - { - state.set_event(SMBEvent::WriteQueueCntExceeded); + if let Some(ev) = smb_queue_limit_event( + &tdf.file_tracker, + wr.wr_offset, + wr.wr_len as u64, + false, + ) { + // tx just created: set the event on it directly. + tx.set_event(ev); + tx.tx_data.0.updated_ts = true; + tx.tx_data.0.updated_tc = true; + // Chunk rejected before the file was opened: complete + close the + // tx and zero files_opened, else it dangles until teardown. + tx.request_done = true; + tx.response_done = true; + tx.tx_data.0.files_opened = 0; state.set_skip(Direction::ToServer, nbss_remaining); + // The trailing set_file_left also claims these bytes: safe -- records + // only reach here with no chunk armed, and handle_skip runs first. } else { filetracker_newchunk( &mut tdf.file_tracker,