]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
smb/log: configuration option for types logging 12951/head
authorPhilippe Antoine <contact@catenacyber.fr>
Thu, 20 Mar 2025 10:07:05 +0000 (11:07 +0100)
committerPhilippe Antoine <pantoine@oisf.net>
Sat, 5 Apr 2025 19:49:49 +0000 (21:49 +0200)
suricata.yaml output section for smb now parses a types list
and will restrict logging of transactions to these types.

By default, everything still gets logged

Remove unused rs_smb_log_json_request on the way

Ticket: 7620

doc/userguide/output/eve/eve-json-format.rst
rust/src/smb/log.rs
src/output-json-smb.c
suricata.yaml.in

index 171f38eb2f70eebf09646f292e90f5f0dd3b7b42..0693c3aa1eaf0121a6531531105ecfd0440092b1 100644 (file)
@@ -1149,6 +1149,21 @@ SMB Fields
 * "response.native_os" (string): SMB1 native OS string
 * "response.native_lm" (string): SMB1 native Lan Manager string
 
+One can restrict which transactions are logged by using the "types" field in the
+suricata.yaml file. If this field is not specified, all transactions types are logged.
+9 values can be specified with this field as shown below:
+
+Configuration::
+
+    - eve-log:
+        enabled: yes
+        type: file
+        filename: eve.json
+        types:
+          - smb:
+              types: [file, tree_connect, negotiate, dcerpc, create,
+                session_setup, ioctl, rename, set_file_path_info, generic]
+
 Examples of SMB logging:
 
 Pipe open::
index 941e83466c93eebfe0c1e3c931a27ce3381322a2..5cb8ea5762354065f79a427ee0780750850f64f8 100644 (file)
@@ -18,6 +18,8 @@
 use std::str;
 use std::string::String;
 use uuid;
+use suricata_sys::sys::SCConfNode;
+use crate::conf::ConfNode;
 use crate::jsonbuilder::{JsonBuilder, JsonError};
 use crate::smb::smb::*;
 use crate::smb::smb1::*;
@@ -25,6 +27,8 @@ use crate::smb::smb2::*;
 use crate::dcerpc::dcerpc::*;
 use crate::smb::funcs::*;
 use crate::smb::smb_status::*;
+use std::error::Error;
+use std::fmt;
 
 #[cfg(not(feature = "debug"))]
 fn debug_add_progress(_js: &mut JsonBuilder, _tx: &SMBTransaction) -> Result<(), JsonError> { Ok(()) }
@@ -65,8 +69,36 @@ fn guid_to_string(guid: &[u8]) -> String {
     }
 }
 
-fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction) -> Result<(), JsonError>
-{
+// Wrapping error for either jsonbuilder error or our own custom error if
+// tx is not to be logged due to config
+#[derive(Debug)]
+enum SmbLogError {
+    SkippedByConf,
+    Json(JsonError),
+}
+
+impl Error for SmbLogError {}
+
+impl fmt::Display for SmbLogError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            SmbLogError::SkippedByConf => {
+                write!(f, "skipped by configuration")
+            }
+            SmbLogError::Json(j) => j.fmt(f),
+        }
+    }
+}
+
+impl From<JsonError> for SmbLogError {
+    fn from(err: JsonError) -> SmbLogError {
+        SmbLogError::Json(err)
+    }
+}
+
+fn smb_common_header(
+    jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction, flags: u64,
+) -> Result<(), SmbLogError> {
     jsb.set_uint("id", tx.id)?;
 
     if state.dialect != 0 {
@@ -144,6 +176,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
 
     match tx.type_data {
         Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SESSIONSETUP) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             if let Some(ref ntlmssp) = x.ntlmssp {
                 jsb.open_object("ntlmssp")?;
                 let domain = String::from_utf8_lossy(&ntlmssp.domain);
@@ -191,6 +226,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             }
         },
         Some(SMBTransactionTypeData::CREATE(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_CREATE) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             let mut name_raw = x.filename.to_vec();
             name_raw.retain(|&i|i != 0x00);
             if !name_raw.is_empty() {
@@ -230,6 +268,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             jsb.set_string("fuid", &gs)?;
         },
         Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_NEGOTIATE) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             if x.smb_ver == 1 {
                 jsb.open_array("client_dialects")?;
                 for d in &x.dialects {
@@ -260,6 +301,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             }
         },
         Some(SMBTransactionTypeData::TREECONNECT(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_TREECONNECT) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             let share_name = String::from_utf8_lossy(&x.share_name);
             if x.is_pipe {
                 jsb.set_string("named_pipe", &share_name)?;
@@ -292,6 +336,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             }
         },
         Some(SMBTransactionTypeData::FILE(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_FILE) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             let file_name = String::from_utf8_lossy(&x.file_name);
             jsb.set_string("filename", &file_name)?;
             let share_name = String::from_utf8_lossy(&x.share_name);
@@ -300,6 +347,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             jsb.set_string("fuid", &gs)?;
         },
         Some(SMBTransactionTypeData::RENAME(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_RENAME) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             if tx.vercmd.get_version() == 2 {
                 jsb.open_object("set_info")?;
                 jsb.set_string("class", "FILE_INFO")?;
@@ -317,6 +367,9 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             jsb.set_string("fuid", &gs)?;
         },
         Some(SMBTransactionTypeData::DCERPC(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_DCERPC) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             jsb.open_object("dcerpc")?;
             if x.req_set {
                 jsb.set_string("request", &dcerpc_type_string(x.req_cmd))?;
@@ -400,9 +453,15 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             jsb.close()?;
         }
         Some(SMBTransactionTypeData::IOCTL(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_IOCTL) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             jsb.set_string("function", &fsctl_func_to_string(x.func))?;
         },
         Some(SMBTransactionTypeData::SETFILEPATHINFO(ref x)) => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_SETFILEPATHINFO) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
             let mut name_raw = x.filename.to_vec();
             name_raw.retain(|&i|i != 0x00);
             if !name_raw.is_empty() {
@@ -439,14 +498,74 @@ fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransactio
             let gs = fuid_to_string(&x.fid);
             jsb.set_string("fuid", &gs)?;
         },
-        _ => {  },
+        None => {
+            if flags != SMB_LOG_DEFAULT_ALL && (flags & SMB_LOG_TYPE_GENERIC) == 0 {
+                return Err(SmbLogError::SkippedByConf);
+            }
+        },
     }
     return Ok(());
 }
 
 #[no_mangle]
-pub extern "C" fn SCSmbLogJsonResponse(jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction) -> bool
-{
-    smb_common_header(jsb, state, tx).is_ok()
+pub extern "C" fn SCSmbLogJsonResponse(
+    jsb: &mut JsonBuilder, state: &mut SMBState, tx: &SMBTransaction, flags: u64,
+) -> bool {
+    smb_common_header(jsb, state, tx, flags).is_ok()
+}
+
+// Flag constants for logging types
+const SMB_LOG_TYPE_FILE: u64 = BIT_U64!(0);
+const SMB_LOG_TYPE_TREECONNECT: u64 = BIT_U64!(1);
+const SMB_LOG_TYPE_NEGOTIATE: u64 = BIT_U64!(2);
+const SMB_LOG_TYPE_DCERPC: u64 = BIT_U64!(3);
+const SMB_LOG_TYPE_CREATE: u64 = BIT_U64!(4);
+const SMB_LOG_TYPE_SESSIONSETUP: u64 = BIT_U64!(5);
+const SMB_LOG_TYPE_IOCTL: u64 = BIT_U64!(6);
+const SMB_LOG_TYPE_RENAME: u64 = BIT_U64!(7);
+const SMB_LOG_TYPE_SETFILEPATHINFO: u64 = BIT_U64!(8);
+const SMB_LOG_TYPE_GENERIC: u64 = BIT_U64!(9);
+const SMB_LOG_DEFAULT_ALL: u64 = 0;
+
+fn get_smb_log_type_from_str(s: &str) -> Option<u64> {
+    match s {
+        "file" => Some(SMB_LOG_TYPE_FILE),
+        "tree_connect" => Some(SMB_LOG_TYPE_TREECONNECT),
+        "negotiate" => Some(SMB_LOG_TYPE_NEGOTIATE),
+        "dcerpc" => Some(SMB_LOG_TYPE_DCERPC),
+        "create" => Some(SMB_LOG_TYPE_CREATE),
+        "session_setup" => Some(SMB_LOG_TYPE_SESSIONSETUP),
+        "ioctl" => Some(SMB_LOG_TYPE_IOCTL),
+        "rename" => Some(SMB_LOG_TYPE_RENAME),
+        "set_file_path_info" => Some(SMB_LOG_TYPE_SETFILEPATHINFO),
+        "generic" => Some(SMB_LOG_TYPE_GENERIC),
+        _ => None,
+    }
 }
 
+#[no_mangle]
+pub extern "C" fn SCSmbLogParseConfig(conf: *const SCConfNode) -> u64 {
+    let conf = ConfNode::wrap(conf);
+    if let Some(node) = conf.get_child_node("types") {
+        // iterate smb.types list of types
+        let mut r = SMB_LOG_DEFAULT_ALL;
+        let mut node = node.first();
+        loop {
+            if node.is_none() {
+                break;
+            }
+            let nodeu = node.unwrap();
+            if let Some(f) = get_smb_log_type_from_str(nodeu.value()) {
+                r |= f;
+            } else {
+                SCLogWarning!("unknown type for smb logging: {}", nodeu.value());
+            }
+            node = nodeu.next();
+        }
+        if r == SMB_LOG_DEFAULT_ALL {
+            SCLogWarning!("empty types list for smb is interpreted as logging all");
+        }
+        return r;
+    }
+    return SMB_LOG_DEFAULT_ALL;
+}
index df4927fb43d9fcd5277551bc6a3b581ae40b3eff..528df3d539be3b840d03ebd1e6592241b223c5c4 100644 (file)
@@ -37,29 +37,45 @@ bool EveSMBAddMetadata(const Flow *f, uint64_t tx_id, SCJsonBuilder *jb)
     if (state) {
         SMBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_SMB, state, tx_id);
         if (tx) {
-            return SCSmbLogJsonResponse(jb, state, tx);
+            // flags 0 means log all
+            return SCSmbLogJsonResponse(jb, state, tx, 0);
         }
     }
     return false;
 }
 
+typedef struct LogSmbFileCtx_ {
+    uint64_t flags;
+    // generic context needed for init by CreateEveThreadCtx
+    // comes from parent in SMBLogInitSub
+    OutputJsonCtx *eve_ctx;
+} LogSmbFileCtx;
+
+// wrapper structure
+typedef struct LogSmbLogThread_ {
+    // generic structure
+    OutputJsonThreadCtx *ctx;
+    // smb-specific structure
+    LogSmbFileCtx *smblog_ctx;
+} LogSmbLogThread;
+
 static int JsonSMBLogger(ThreadVars *tv, void *thread_data,
     const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
 {
-    OutputJsonThreadCtx *thread = thread_data;
+    LogSmbLogThread *thread = thread_data;
 
-    SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx);
+    SCJsonBuilder *jb = CreateEveHeader(p, LOG_DIR_FLOW, "smb", NULL, thread->ctx->ctx);
     if (unlikely(jb == NULL)) {
         return TM_ECODE_FAILED;
     }
 
     SCJbOpenObject(jb, "smb");
-    if (!SCSmbLogJsonResponse(jb, state, tx)) {
+    if (!SCSmbLogJsonResponse(jb, state, tx, thread->smblog_ctx->flags)) {
         goto error;
     }
     SCJbClose(jb);
 
-    OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread);
+    OutputJsonBuilderBuffer(tv, p, p->flow, jb, thread->ctx);
 
     SCJbFree(jb);
     return TM_ECODE_OK;
@@ -69,18 +85,73 @@ error:
     return TM_ECODE_FAILED;
 }
 
+static void LogSmbLogDeInitCtxSub(OutputCtx *output_ctx)
+{
+    LogSmbFileCtx *smblog_ctx = (LogSmbFileCtx *)output_ctx->data;
+    SCFree(smblog_ctx);
+    SCFree(output_ctx);
+}
+
 static OutputInitResult SMBLogInitSub(SCConfNode *conf, OutputCtx *parent_ctx)
 {
     AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SMB);
     AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_SMB);
-    return OutputJsonLogInitSub(conf, parent_ctx);
+    OutputInitResult r = OutputJsonLogInitSub(conf, parent_ctx);
+    if (r.ok) {
+        // generic init is ok, try smb-specific one
+        LogSmbFileCtx *smblog_ctx = SCCalloc(1, sizeof(LogSmbFileCtx));
+        if (unlikely(smblog_ctx == NULL)) {
+            SCFree(r.ctx);
+            r.ctx = NULL;
+            r.ok = false;
+            return r;
+        }
+        smblog_ctx->eve_ctx = parent_ctx->data;
+        // parse config for flags/types to log
+        smblog_ctx->flags = SCSmbLogParseConfig(conf);
+        r.ctx->data = smblog_ctx;
+        r.ctx->DeInit = LogSmbLogDeInitCtxSub;
+    }
+    return r;
+}
+
+static TmEcode LogSmbLogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+    if (initdata == NULL) {
+        return TM_ECODE_FAILED;
+    }
+
+    LogSmbLogThread *aft = SCCalloc(1, sizeof(LogSmbLogThread));
+    if (unlikely(aft == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    aft->smblog_ctx = ((OutputCtx *)initdata)->data;
+    aft->ctx = CreateEveThreadCtx(t, aft->smblog_ctx->eve_ctx);
+    if (!aft->ctx) {
+        SCFree(aft);
+        return TM_ECODE_FAILED;
+    }
+
+    *data = (void *)aft;
+    return TM_ECODE_OK;
+}
+
+// LogSmbLogThread structure wraps a generic OutputJsonThreadCtx
+// created by CreateEveThreadCtx
+static TmEcode LogSmbLogThreadDeinit(ThreadVars *t, void *data)
+{
+    LogSmbLogThread *aft = (LogSmbLogThread *)data;
+    TmEcode r = JsonLogThreadDeinit(t, aft->ctx);
+    SCFree(aft);
+    return r;
 }
 
 void JsonSMBLogRegister(void)
 {
     /* Register as an eve sub-module. */
     OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonSMBLog", "eve-log.smb", SMBLogInitSub,
-            ALPROTO_SMB, JsonSMBLogger, JsonLogThreadInit, JsonLogThreadDeinit);
+            ALPROTO_SMB, JsonSMBLogger, LogSmbLogThreadInit, LogSmbLogThreadDeinit);
 
     SCLogDebug("SMB JSON logger registered.");
 }
index 5d366283a73cd5fc0fd33b887f3bdb0912454b64..2a16a563c4936ff0d88fa5e3d1698a00845f3efd 100644 (file)
@@ -318,7 +318,10 @@ outputs:
         - ftp
         - rdp
         - nfs
-        - smb
+        - smb:
+            # restrict to only certain types in the following list
+            #types: [file, tree_connect, negotiate, dcerpc, create,
+            #  session_setup, ioctl, rename, set_file_path_info, generic]
         - tftp
         - ike
         - dcerpc