diff --git a/scripts/base/frameworks/notice/weird.zeek b/scripts/base/frameworks/notice/weird.zeek
index 47a6b6dde0..919f683123 100644
--- a/scripts/base/frameworks/notice/weird.zeek
+++ b/scripts/base/frameworks/notice/weird.zeek
@@ -210,6 +210,7 @@ export {
["spontaneous_FIN"] = ACTION_IGNORE,
["spontaneous_RST"] = ACTION_IGNORE,
["SMB_parsing_error"] = ACTION_LOG,
+ ["SMB_discarded_messages_state"] = ACTION_LOG,
["no_smb_session_using_parsesambamsg"] = ACTION_LOG,
["smb_andx_command_failed_to_parse"] = ACTION_LOG,
["smb_tree_connect_andx_response_without_tree"] = ACTION_LOG_PER_CONN,
diff --git a/scripts/base/protocols/smb/main.zeek b/scripts/base/protocols/smb/main.zeek
index ed129f8ce1..91e27acffa 100644
--- a/scripts/base/protocols/smb/main.zeek
+++ b/scripts/base/protocols/smb/main.zeek
@@ -44,6 +44,13 @@ export {
PRINT_CLOSE,
};
+ ## Whether to reset a connection's SMB script state whenever a
+ ## :zeek:see:`smb2_discarded_messages_state` event is raised.
+ ##
+ ## This setting protects from unbounded script state growth in
+ ## environments with high capture loss or traffic anomalies.
+ option enable_clear_script_state = T;
+
## This record is for the smb_files.log
type FileInfo: record {
## Time when the file was first discovered.
diff --git a/scripts/base/protocols/smb/smb2-main.zeek b/scripts/base/protocols/smb/smb2-main.zeek
index 8ccaa2df84..31f0a7704a 100644
--- a/scripts/base/protocols/smb/smb2-main.zeek
+++ b/scripts/base/protocols/smb/smb2-main.zeek
@@ -1,3 +1,5 @@
+@load base/frameworks/notice/weird
+
@load ./main
module SMB2;
@@ -344,3 +346,25 @@ event smb2_close_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID)
#Reporter::warning("attempting to close an unknown file!");
}
}
+
+event smb2_discarded_messages_state(c: connection, state: string)
+ {
+ if ( ! c?$smb_state )
+ return;
+
+ local addl = fmt("state=%s fid_map=%s tid_map=%s pending_cmds=%s pipe_map=%s",
+ state, |c$smb_state$fid_map|, |c$smb_state$tid_map|,
+ |c$smb_state$pending_cmds|, |c$smb_state$pipe_map|);
+ Reporter::conn_weird("SMB_discarded_messages_state", c, addl, "SMB2");
+
+ if ( ! SMB::enable_clear_script_state )
+ return;
+
+ # Wipe out script-level state for this connection.
+ c$smb_state$fid_map = table();
+ c$smb_state$pending_cmds = table();
+ # Not expected to grow overly large and the original
+ # zeek-smb-clear-state package didn't reset these either.
+ # c$smb_state$tid_map = table();
+ # c$smb_state$pipe_map = table();
+ }
diff --git a/testing/btest/Baseline/plugins.hooks/output b/testing/btest/Baseline/plugins.hooks/output
index 97b230190e..8ab256052c 100644
--- a/testing/btest/Baseline/plugins.hooks/output
+++ b/testing/btest/Baseline/plugins.hooks/output
@@ -575,6 +575,7 @@
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (RDP::disable_analyzer_after_detection, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (RDP::rdp_check_interval, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (SIP::sip_methods, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
+0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (SMB::enable_clear_script_state, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (SMB::logged_file_actions, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (SMTP::mail_path_capture, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
0.000000 MetaHookPost CallFunction(Option::set_change_handler, , (SMTP::mail_transaction_validation, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)) ->
@@ -2194,6 +2195,7 @@
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (RDP::disable_analyzer_after_detection, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (RDP::rdp_check_interval, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (SIP::sip_methods, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
+0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (SMB::enable_clear_script_state, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (SMB::logged_file_actions, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (SMTP::mail_path_capture, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
0.000000 MetaHookPre CallFunction(Option::set_change_handler, , (SMTP::mail_transaction_validation, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100))
@@ -3812,6 +3814,7 @@
0.000000 | HookCallFunction Option::set_change_handler(RDP::disable_analyzer_after_detection, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(RDP::rdp_check_interval, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(SIP::sip_methods, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
+0.000000 | HookCallFunction Option::set_change_handler(SMB::enable_clear_script_state, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(SMB::logged_file_actions, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(SMTP::mail_path_capture, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
0.000000 | HookCallFunction Option::set_change_handler(SMTP::mail_transaction_validation, Config::config_option_changed{ if ( == Config::location) return (Config::new_value)Config::log = Config::Info($ts=network_time(), $id=Config::ID, $old_value=Config::format_value(lookup_ID(Config::ID)), $new_value=Config::format_value(Config::new_value))if ( != Config::location) Config::log$location = Config::locationLog::write(Config::LOG, to_any_coerceConfig::log)return (Config::new_value)}, -100)
diff --git a/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/out b/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/out
new file mode 100644
index 0000000000..524f00fa21
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/out
@@ -0,0 +1,25 @@
+### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, read, 15
+smb2_discarded_messages_state after, read, 0
+smb2_discarded_messages_state before, tree, 5
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, read, 15
+smb2_discarded_messages_state after, read, 0
+smb2_discarded_messages_state before, tree, 5
+smb2_discarded_messages_state after, tree, 0
+smb2_discarded_messages_state before, tree, 20
+smb2_discarded_messages_state after, tree, 0
diff --git a/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/weird.log b/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/weird.log
new file mode 100644
index 0000000000..f7d257af1b
--- /dev/null
+++ b/testing/btest/Baseline/scripts.base.protocols.smb.smb2-max-pending-messages/weird.log
@@ -0,0 +1,11 @@
+### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
+#separator \x09
+#set_separator ,
+#empty_field (empty)
+#unset_field -
+#path weird
+#open XXXX-XX-XX-XX-XX-XX
+#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p name addl notice peer source
+#types time string addr port addr port string string bool string string
+XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 34884 127.0.0.1 445 SMB_discarded_messages_state state=tree fid_map=0 tid_map=2 pending_cmds=20 pipe_map=0 F zeek SMB2
+#close XXXX-XX-XX-XX-XX-XX
diff --git a/testing/btest/Traces/smb/smb2_100_small_files.pcap b/testing/btest/Traces/smb/smb2_100_small_files.pcap
new file mode 100644
index 0000000000..4bc38dc36a
Binary files /dev/null and b/testing/btest/Traces/smb/smb2_100_small_files.pcap differ
diff --git a/testing/btest/scripts/base/protocols/smb/smb2-max-pending-messages.test b/testing/btest/scripts/base/protocols/smb/smb2-max-pending-messages.test
new file mode 100644
index 0000000000..e2558ec83d
--- /dev/null
+++ b/testing/btest/scripts/base/protocols/smb/smb2-max-pending-messages.test
@@ -0,0 +1,18 @@
+# @TEST-DOC: Pcap contains 100 file transfers (read requests), force BPF filtering such that the responses aren't seen and we have state growth. Verify a low SMB::max_pending_messages triggers, logs a weird and that script-land message state is reset.
+# @TEST-EXEC: zeek -b -C -r $TRACES/smb/smb2_100_small_files.pcap -f 'src port not 445 or tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0' %INPUT >out
+# @TEST-EXEC: btest-diff weird.log
+# @TEST-EXEC: btest-diff out
+
+@load base/protocols/smb
+
+redef SMB::max_pending_messages = 20;
+
+event smb2_discarded_messages_state(c: connection, request: string) &priority=10
+ {
+ print "smb2_discarded_messages_state before", request, |c$smb_state$pending_cmds|;
+ }
+
+event smb2_discarded_messages_state(c: connection, request: string) &priority=-10
+ {
+ print "smb2_discarded_messages_state after", request, |c$smb_state$pending_cmds|;
+ }