]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
smb: use lru for guid2name map; rename
authorVictor Julien <vjulien@oisf.net>
Tue, 17 Sep 2024 15:10:19 +0000 (17:10 +0200)
committerVictor Julien <victor@inliniac.net>
Wed, 6 Nov 2024 20:33:33 +0000 (21:33 +0100)
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.

rust/Cargo.lock.in
rust/Cargo.toml.in
rust/src/lib.rs
rust/src/smb/debug.rs
rust/src/smb/smb.rs
rust/src/smb/smb1.rs
rust/src/smb/smb2.rs

index efdf1c0a66d609481a241e75151f1707dce2eccb..b1ed92b8901376774ca59edd7d0d3925d52b19ef 100644 (file)
@@ -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",
index f99ac2728d19424a4bebfe42a66d8cd78cb85044..aa734476dff86c627da46bdb2c8fa2e0f3d052eb 100644 (file)
@@ -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 }
index 7a99cd2532e5368ae9ab21988630fb5b5487607e..7b389f7b88fee0e8d7cfcb65af384c22db2750bf 100644 (file)
@@ -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;
index 86799dd7fa1da7a32cd625742a5b353f9b36b4e1..f87180b6a8f3719263515caff7a92740d7e68190 100644 (file)
@@ -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());
     }
 }
index f80191b03bc357ac6e82ced523e47f5432b1a4bc..e69399c71891aac2637454c1dad347cbafae4b3f 100644 (file)
@@ -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<SMBCommonHdr, Vec<u8>>,
+
     /// map guid to filename
-    pub guid2name_map: HashMap<Vec<u8>, Vec<u8>>,
+    ///
+    /// 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<u8>, Vec<u8>>,
+
     /// map ssn key to read offset
     pub ssn2vecoffset_map: HashMap<SMBCommonHdr, SMBFileGUIDOffset>,
 
@@ -754,13 +768,19 @@ impl State<SMBTransaction> 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::<usize>() {
+                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.");
     }
index 9f3f1ea21b196110666718c060942b162d96766b..731bedeb33908fa8954c97ea03c17a16a8899221 100644 (file)
@@ -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"<unknown>".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"<unknown>".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"<unknown>".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(),
                     };
index 0ccfdf0d88ab856f71469e810871ebd727b04c81..f6c0d924f46ea535a44008dd47a43d3f55c82e43 100644 (file)
@@ -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"<unknown>".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"<unknown>".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");
                     }