]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
smb: handle file transactions post-GAP 4506/head
authorVictor Julien <victor@inliniac.net>
Tue, 21 Jan 2020 11:20:40 +0000 (12:20 +0100)
committerVictor Julien <victor@inliniac.net>
Tue, 28 Jan 2020 15:04:23 +0000 (16:04 +0100)
After a GAP all normal transactions are closed. File transactions
are left open as they can handle GAPs in principle. However, the
GAP might have contained the closing of a file and therefore it
may remain active until the end of the flow.

This patch introduces a time based heuristic for these transactions.
After the GAP all file transactions are stamped with the current
timestamp. If 60 seconds later a file has seen no update, its marked
as closed.

This is meant to fix resource starvation issues observed in long
running SMB sessions where packet loss was causing GAPs.

rust/src/smb/files.rs
rust/src/smb/smb.rs

index 29f051fb99fcf69bf836f4911c2a6f3978e7fea3..b8667e3fb87f5e51d15f22230638c843b2ebea9f 100644 (file)
@@ -30,6 +30,9 @@ pub struct SMBTransactionFile {
     pub file_name: Vec<u8>,
     pub share_name: Vec<u8>,
     pub file_tracker: FileTransferTracker,
+    /// after a gap, this will be set to a time in the future. If the file
+    /// receives no updates before that, it will be considered complete.
+    pub post_gap_ts: u64,
 }
 
 impl SMBTransactionFile {
@@ -40,6 +43,7 @@ impl SMBTransactionFile {
             file_name: Vec::new(),
             share_name: Vec::new(),
             file_tracker: FileTransferTracker::new(),
+            post_gap_ts: 0,
         }
     }
 }
@@ -201,6 +205,11 @@ impl SMBState {
                         }
                     }
 
+                    // reset timestamp if we get called after a gap
+                    if tdf.post_gap_ts > 0 {
+                        tdf.post_gap_ts = 0;
+                    }
+
                     let file_data = &data[0..data_to_handle_len];
                     let cs = tdf.file_tracker.update(files, flags, file_data, gap_size);
                     cs
index b74bf2e91461165e8fafd0220ca124f985c38ec6..2939108dd512d63083f950c18913936b8f3f0f20 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2017 Open Information Security Foundation
+/* Copyright (C) 2017-2020 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -784,6 +784,10 @@ pub struct SMBState<> {
     pub ts_trunc: bool, // no more data for TOSERVER
     pub tc_trunc: bool, // no more data for TOCLIENT
 
+    /// true as long as we have file txs that are in a post-gap
+    /// state. It means we'll do extra house keeping for those.
+    check_post_gap_file_txs: bool,
+
     /// transactions list
     pub transactions: Vec<SMBTransaction>,
 
@@ -798,6 +802,10 @@ pub struct SMBState<> {
     /// dcerpc interfaces, stored here to be able to match
     /// them while inspecting DCERPC REQUEST txs
     pub dcerpc_ifaces: Option<Vec<DCERPCIface>>,
+
+    /// Timestamp in seconds of last update. This is packet time,
+    /// potentially coming from pcaps.
+    ts: u64,
 }
 
 impl SMBState {
@@ -824,11 +832,13 @@ impl SMBState {
             tc_gap: false,
             ts_trunc: false,
             tc_trunc: false,
+            check_post_gap_file_txs: false,
             transactions: Vec::new(),
             tx_id:0,
             dialect:0,
             dialect_vec: None,
             dcerpc_ifaces: None,
+            ts: 0,
         }
     }
 
@@ -1143,9 +1153,29 @@ impl SMBState {
         (&name, is_dcerpc)
     }
 
+    fn post_gap_housekeeping_for_files(&mut self)
+    {
+        let mut post_gap_txs = false;
+        for tx in &mut self.transactions {
+            if let Some(SMBTransactionTypeData::FILE(ref f)) = tx.type_data {
+                if f.post_gap_ts > 0 {
+                    if self.ts > f.post_gap_ts {
+                        tx.request_done = true;
+                        tx.response_done = true;
+                    } else {
+                        post_gap_txs = true;
+                    }
+                }
+            }
+        }
+        self.check_post_gap_file_txs = post_gap_txs;
+    }
+
     /* after a gap we will consider all transactions complete for our
      * direction. File transfer transactions are an exception. Those
-     * can handle gaps. */
+     * can handle gaps. For the file transactions we set the current
+     * (flow) time and prune them in 60 seconds if no update for them
+     * was received. */
     fn post_gap_housekeeping(&mut self, dir: u8)
     {
         if self.ts_ssn_gap && dir == STREAM_TOSERVER {
@@ -1154,8 +1184,13 @@ impl SMBState {
                     SCLogDebug!("post_gap_housekeeping: done");
                     break;
                 }
-                if let Some(SMBTransactionTypeData::FILE(_)) = tx.type_data {
-                    // leaving FILE txs open as they can deal with gaps.
+                if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data {
+                    // leaving FILE txs open as they can deal with gaps. We
+                    // remove them after 60 seconds of no activity though.
+                    if f.post_gap_ts == 0 {
+                        f.post_gap_ts = self.ts + 60;
+                        self.check_post_gap_file_txs = true;
+                    }
                 } else {
                     SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id);
                     tx.request_done = true;
@@ -1167,8 +1202,13 @@ impl SMBState {
                     SCLogDebug!("post_gap_housekeeping: done");
                     break;
                 }
-                if let Some(SMBTransactionTypeData::FILE(_)) = tx.type_data {
-                    // leaving FILE txs open as they can deal with gaps.
+                if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data {
+                    // leaving FILE txs open as they can deal with gaps. We
+                    // remove them after 60 seconds of no activity though.
+                    if f.post_gap_ts == 0 {
+                        f.post_gap_ts = self.ts + 60;
+                        self.check_post_gap_file_txs = true;
+                    }
                 } else {
                     SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id);
                     tx.request_done = true;
@@ -1457,6 +1497,9 @@ impl SMBState {
         };
 
         self.post_gap_housekeeping(STREAM_TOSERVER);
+        if self.check_post_gap_file_txs {
+            self.post_gap_housekeeping_for_files();
+        }
         0
     }
 
@@ -1684,6 +1727,9 @@ impl SMBState {
             }
         };
         self.post_gap_housekeeping(STREAM_TOCLIENT);
+        if self.check_post_gap_file_txs {
+            self.post_gap_housekeeping_for_files();
+        }
         self._debug_tx_stats();
         0
     }
@@ -1783,7 +1829,7 @@ pub extern "C" fn rs_smb_state_free(state: *mut std::os::raw::c_void) {
 
 /// C binding parse a SMB request. Returns 1 on success, -1 on failure.
 #[no_mangle]
-pub extern "C" fn rs_smb_parse_request_tcp(_flow: *mut Flow,
+pub extern "C" fn rs_smb_parse_request_tcp(flow: &mut Flow,
                                        state: &mut SMBState,
                                        _pstate: *mut std::os::raw::c_void,
                                        input: *const u8,
@@ -1800,6 +1846,7 @@ pub extern "C" fn rs_smb_parse_request_tcp(_flow: *mut Flow,
         state.ts_gap = true;
     }
 
+    state.ts = flow.get_last_time().as_secs();
     if state.parse_tcp_data_ts(buf) == 0 {
         return 1;
     } else {
@@ -1821,7 +1868,7 @@ pub extern "C" fn rs_smb_parse_request_tcp_gap(
 
 
 #[no_mangle]
-pub extern "C" fn rs_smb_parse_response_tcp(_flow: *mut Flow,
+pub extern "C" fn rs_smb_parse_response_tcp(flow: &mut Flow,
                                         state: &mut SMBState,
                                         _pstate: *mut std::os::raw::c_void,
                                         input: *const u8,
@@ -1838,6 +1885,7 @@ pub extern "C" fn rs_smb_parse_response_tcp(_flow: *mut Flow,
         state.tc_gap = true;
     }
 
+    state.ts = flow.get_last_time().as_secs();
     if state.parse_tcp_data_tc(buf) == 0 {
         return 1;
     } else {