From: Victor Julien Date: Tue, 17 Sep 2024 15:10:19 +0000 (+0200) Subject: smb: use lru for guid2name map; rename X-Git-Tag: suricata-8.0.0-beta1~721 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91828ec00bdfe65ea2a01e3d11c5520fcf8306ff;p=thirdparty%2Fsuricata.git smb: use lru for guid2name map; rename Use `lru` crate. Rename to reflect this. Add `app-layer.protocols.smb.max-guid-cache-size` to control the max size of the LRU cache. Ticket: #5672. --- diff --git a/rust/Cargo.lock.in b/rust/Cargo.lock.in index efdf1c0a66..b1ed92b890 100644 --- a/rust/Cargo.lock.in +++ b/rust/Cargo.lock.in @@ -67,6 +67,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "asn1-rs" version = "0.6.2" @@ -296,6 +302,12 @@ dependencies = [ "num-traits 0.1.43", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "failure" version = "0.1.8" @@ -327,6 +339,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "fs_extra" version = "1.3.0" @@ -364,6 +382,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hex" version = "0.4.3" @@ -438,6 +467,15 @@ version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "lzma-rs" version = "0.2.0" @@ -973,6 +1011,7 @@ dependencies = [ "lazy_static", "ldap-parser", "libc", + "lru", "lzma-rs", "md-5", "memchr", diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index f99ac2728d..aa734476df 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -40,6 +40,7 @@ brotli = "~3.4.0" hkdf = "~0.12.3" aes = "~0.7.5" aes-gcm = "~0.9.4" +lru = "~0.12.5" der-parser = { version = "~9.0.0", default-features = false } kerberos-parser = { version = "~0.8.0", default-features = false } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7a99cd2532..7b389f7b88 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -58,6 +58,7 @@ extern crate bitflags; extern crate byteorder; extern crate crc; extern crate memchr; +extern crate lru; #[macro_use] extern crate num_derive; extern crate widestring; diff --git a/rust/src/smb/debug.rs b/rust/src/smb/debug.rs index 86799dd7fa..f87180b6a8 100644 --- a/rust/src/smb/debug.rs +++ b/rust/src/smb/debug.rs @@ -69,6 +69,14 @@ impl SMBState { #[cfg(feature = "debug")] pub fn _debug_state_stats(&self) { - SCLogDebug!("ssn2vec_map {} guid2name_map {} ssn2vecoffset_map {} ssn2tree_map {} ssnguid2vec_map {} file_ts_guid {} file_tc_guid {} transactions {}", self.ssn2vec_map.len(), self.guid2name_map.len(), self.ssn2vecoffset_map.len(), self.ssn2tree_map.len(), self.ssnguid2vec_map.len(), self.file_ts_guid.len(), self.file_tc_guid.len(), self.transactions.len()); + SCLogDebug!("ssn2vec_map {} guid2name_cache {} ssn2vecoffset_map {} ssn2tree_map {} ssnguid2vec_map {} file_ts_guid {} file_tc_guid {} transactions {}", + self.ssn2vec_map.len(), + self.guid2name_cache.len(), + self.ssn2vecoffset_map.len(), + self.ssn2tree_map.len(), + self.ssnguid2vec_map.len(), + self.file_ts_guid.len(), + self.file_tc_guid.len(), + self.transactions.len()); } } diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs index f80191b03b..e69399c718 100644 --- a/rust/src/smb/smb.rs +++ b/rust/src/smb/smb.rs @@ -34,6 +34,9 @@ use std::collections::VecDeque; use nom7::{Err, Needed}; use nom7::error::{make_error, ErrorKind}; +use lru::LruCache; +use std::num::NonZeroUsize; + use crate::core::*; use crate::applayer; use crate::applayer::*; @@ -79,6 +82,8 @@ pub static mut SMB_CFG_MAX_READ_QUEUE_CNT: u32 = 64; pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216; pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864; pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64; +/// max size of the per state guid2name cache +pub static mut SMB_CFG_MAX_GUID_CACHE_SIZE: usize = 1024; static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN; @@ -681,14 +686,23 @@ pub fn u32_as_bytes(i: u32) -> [u8;4] { return [o1, o2, o3, o4] } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct SMBState<> { pub state_data: AppLayerStateData, /// map ssn/tree/msgid to vec (guid/name/share) pub ssn2vec_map: HashMap>, + /// map guid to filename - pub guid2name_map: HashMap, Vec>, + /// + /// Lifecycle of the members: + /// - Added by CREATE responses + /// - Removed by CLOSE requests + /// - Post GAP logic removes based on timestamp, as the CLOSE + /// commands may have been missed. + /// + pub guid2name_cache: LruCache, Vec>, + /// map ssn key to read offset pub ssn2vecoffset_map: HashMap, @@ -754,13 +768,19 @@ impl State for SMBState { } } +impl Default for SMBState { + fn default() -> Self { + Self::new() + } +} + impl SMBState { /// Allocation function for a new TLS parser instance pub fn new() -> Self { Self { state_data:AppLayerStateData::new(), ssn2vec_map:HashMap::new(), - guid2name_map:HashMap::new(), + guid2name_cache:LruCache::new(NonZeroUsize::new(unsafe { SMB_CFG_MAX_GUID_CACHE_SIZE }).unwrap()), ssn2vecoffset_map:HashMap::new(), ssn2tree_map:HashMap::new(), ssnguid2vec_map:HashMap::new(), @@ -784,8 +804,9 @@ impl SMBState { dialect:0, dialect_vec: None, dcerpc_ifaces: None, + max_read_size: 0, + max_write_size: 0, ts: 0, - ..Default::default() } } @@ -1042,9 +1063,9 @@ impl SMBState { return tx_ref.unwrap(); } - pub fn get_service_for_guid(&self, guid: &[u8]) -> (&'static str, bool) + pub fn get_service_for_guid(&mut self, guid: &[u8]) -> (&'static str, bool) { - let (name, is_dcerpc) = match self.guid2name_map.get(guid) { + let (name, is_dcerpc) = match self.guid2name_cache.get(guid) { Some(n) => { let mut s = n.as_slice(); // skip leading \ if we have it @@ -2422,10 +2443,23 @@ pub unsafe extern "C" fn rs_smb_register_parser() { SCLogError!("Invalid value for smb.max-tx"); } } + let retval = conf_get("app-layer.protocols.smb.max-guid-cache-size"); + if let Some(val) = retval { + if let Ok(v) = val.parse::() { + if v > 0 { + SMB_CFG_MAX_GUID_CACHE_SIZE = v; + } else { + SCLogError!("Invalid max-guid-cache-size value"); + } + } else { + SCLogError!("Invalid max-guid-cache-size value"); + } + } SCLogConfig!("read: max record size: {}, max queued chunks {}, max queued size {}", SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, SMB_CFG_MAX_READ_QUEUE_SIZE); SCLogConfig!("write: max record size: {}, max queued chunks {}, max queued size {}", SMB_CFG_MAX_WRITE_SIZE, SMB_CFG_MAX_WRITE_QUEUE_CNT, SMB_CFG_MAX_WRITE_QUEUE_SIZE); + SCLogConfig!("guid: max cache size: {}", SMB_CFG_MAX_GUID_CACHE_SIZE); } else { SCLogDebug!("Protocol detector and parser disabled for SMB."); } diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs index 9f3f1ea21b..731bedeb33 100644 --- a/rust/src/smb/smb1.rs +++ b/rust/src/smb/smb1.rs @@ -306,7 +306,7 @@ fn smb1_request_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, and let mut frankenfid = pd.fid.to_vec(); frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); - let filename = match state.guid2name_map.get(&frankenfid) { + let filename = match state.guid2name_cache.get(&frankenfid) { Some(n) => n.to_vec(), None => b"".to_vec(), }; @@ -341,7 +341,7 @@ fn smb1_request_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, and let mut frankenfid = pd.fid.to_vec(); frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); - let oldname = match state.guid2name_map.get(&frankenfid) { + let oldname = match state.guid2name_cache.get(&frankenfid) { Some(n) => n.to_vec(), None => b"".to_vec(), }; @@ -536,7 +536,7 @@ fn smb1_request_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, and let mut fid = cd.fid.to_vec(); fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); - let _name = state.guid2name_map.remove(&fid); + let _name = state.guid2name_cache.pop(&fid); state.ssn2vec_map.insert(SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID), fid.to_vec()); SCLogDebug!("closing FID {:?}/{:?}", cd.fid, fid); @@ -734,7 +734,7 @@ fn smb1_response_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, an fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); SCLogDebug!("SMB1_COMMAND_NT_CREATE_ANDX fid {:?}", fid); SCLogDebug!("fid {:?} name {:?}", fid, p); - state.guid2name_map.insert(fid, p); + _ = state.guid2name_cache.put(fid, p); } else { SCLogDebug!("SMBv1 response: GUID NOT FOUND"); } @@ -954,7 +954,7 @@ pub fn smb1_write_request_record(state: &mut SMBState, r: &SmbRecord, andx_offse SCLogDebug!("SMBv1 WRITE: FID {:?} offset {}", file_fid, rd.offset); - let file_name = match state.guid2name_map.get(&file_fid) { + let file_name = match state.guid2name_cache.get(&file_fid) { Some(n) => n.to_vec(), None => b"".to_vec(), }; @@ -1010,7 +1010,7 @@ pub fn smb1_write_request_record(state: &mut SMBState, r: &SmbRecord, andx_offse state.set_file_left(Direction::ToServer, rd.len, rd.data.len() as u32, file_fid.to_vec()); if command == SMB1_COMMAND_WRITE_AND_CLOSE { - let _name = state.guid2name_map.remove(&file_fid); + let _name = state.guid2name_cache.pop(&file_fid); SCLogDebug!("closing FID {:?}", file_fid); smb1_close_file(state, &file_fid, Direction::ToServer); } @@ -1055,7 +1055,7 @@ pub fn smb1_read_response_record(state: &mut SMBState, r: &SmbRecord, andx_offse _ => { (false, Vec::new()) }, }; if !is_pipe { - let file_name = match state.guid2name_map.get(&file_fid) { + let file_name = match state.guid2name_cache.get(&file_fid) { Some(n) => n.to_vec(), None => Vec::new(), }; diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs index 0ccfdf0d88..f6c0d924f4 100644 --- a/rust/src/smb/smb2.rs +++ b/rust/src/smb/smb2.rs @@ -210,7 +210,7 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); state.ssn2tree_map.insert(tree_key, tree); if !is_dcerpc { - state.guid2name_map.insert(file_guid.to_vec(), b"suricata::dcerpc".to_vec()); + _ = state.guid2name_cache.put(file_guid.to_vec(), b"suricata::dcerpc".to_vec()); } is_pipe = true; is_dcerpc = true; @@ -228,7 +228,7 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema SCLogDebug!("non-DCERPC pipe"); state.set_skip(Direction::ToClient, nbss_remaining); } else { - let file_name = match state.guid2name_map.get(&file_guid) { + let file_name = match state.guid2name_cache.get(&file_guid) { Some(n) => { n.to_vec() } None => { b"".to_vec() } }; @@ -302,7 +302,7 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); let file_guid = wr.guid.to_vec(); - let file_name = match state.guid2name_map.get(&file_guid) { + let file_name = match state.guid2name_cache.get(&file_guid) { Some(n) => n.to_vec(), None => Vec::new(), }; @@ -354,8 +354,8 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); state.ssn2tree_map.insert(tree_key, tree); if !is_dcerpc { - state.guid2name_map.insert(file_guid.to_vec(), - b"suricata::dcerpc".to_vec()); + _ = state.guid2name_cache.put(file_guid.to_vec(), + b"suricata::dcerpc".to_vec()); } is_pipe = true; is_dcerpc = true; @@ -427,7 +427,7 @@ pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); let mut newname = ren.name.to_vec(); newname.retain(|&i|i != 0x00); - let oldname = match state.guid2name_map.get(rd.guid) { + let oldname = match state.guid2name_cache.get(rd.guid) { Some(n) => { n.to_vec() }, None => { b"".to_vec() }, }; @@ -439,7 +439,7 @@ pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) } Smb2SetInfoRequestData::DISPOSITION(ref dis) => { let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); - let fname = match state.guid2name_map.get(rd.guid) { + let fname = match state.guid2name_cache.get(rd.guid) { Some(n) => { n.to_vec() }, None => { // try to find latest created file in case of chained commands @@ -576,7 +576,7 @@ pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) }, SMB2_COMMAND_CLOSE => { if let Ok((_, cd)) = parse_smb2_request_close(r.data) { - let _name = state.guid2name_map.remove(cd.guid); + let _name = state.guid2name_cache.pop(cd.guid); let found_ts = if let Some(tx) = state.get_file_tx_by_fuid(cd.guid, Direction::ToServer) { if !tx.request_done { @@ -695,7 +695,7 @@ pub fn smb2_response_record(state: &mut SMBState, r: &Smb2Record) let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) { p.retain(|&i|i != 0x00); - state.guid2name_map.insert(cr.guid.to_vec(), p); + _ = state.guid2name_cache.put(cr.guid.to_vec(), p); } else { SCLogDebug!("SMBv2 response: GUID NOT FOUND"); }