smb/dce-rpc: Cleanup DCE-RPC analyzers when fid is closed and limit them

This patch does two things:

1) For SMB close requests, tear down any associated DCE-RPC
   analyzer if one exists.

2) Protect from fid_to_analyzer_map growing unbounded by introducing a
   new SMB::max_dce_rpc_analyzers limit and forcefully wipe the
   analyzers if exceeded. Propagate this to script land as event
   smb_discarded_dce_rpc_analyzers() for additional cleanup.

This is mostly to fix how the binpac SMB analyzer tracks individual
DCE-RPC analyzers per open fid. Connections that re-open the same or
different pipe may currently allocate unbounded number of analyzers.

Closes #3145.
This commit is contained in:
Arne Welzel 2023-06-30 14:27:13 +02:00
parent 1c9038f38d
commit 6517ed94f2
7 changed files with 61 additions and 1 deletions

View file

@ -211,6 +211,7 @@ export {
["spontaneous_RST"] = ACTION_IGNORE,
["SMB_parsing_error"] = ACTION_LOG,
["SMB_discarded_messages_state"] = ACTION_LOG,
["SMB_discarded_dce_rpc_analyzers"] = 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,

View file

@ -3054,6 +3054,12 @@ export {
##
## .. zeek:see:: smb2_discarded_messages_state
const SMB::max_pending_messages = 1000 &redef;
## Maximum number of DCE-RPC analyzers per connection
## before discarding them to avoid unbounded state growth.
##
## .. zeek:see:: smb_discarded_dce_rpc_analyzers
const max_dce_rpc_analyzers = 1000 &redef;
}
module SMB1;

View file

@ -216,6 +216,15 @@ event dce_rpc_response(c: connection, fid: count, ctx_id: count, opnum: count, s
}
}
event smb_discarded_dce_rpc_analyzers(c: connection)
{
# This event is raised when the DCE-RPC analyzers table
# grew too large. Assume things are broken and wipe
# the backing table.
delete c$dce_rpc_backing;
Reporter::conn_weird("SMB_discarded_dce_rpc_analyzers", c, "", "SMB");
}
hook finalize_dce_rpc(c: connection)
{
if ( ! c?$dce_rpc )

View file

@ -1,2 +1,3 @@
const SMB::pipe_filenames: string_set;
const SMB::max_pending_messages: count;
const SMB::max_dce_rpc_analyzers: count;

View file

@ -8,3 +8,13 @@
##
## c: The connection.
event smb_pipe_connect_heuristic%(c: connection%);
## Generated for :abbr:`SMB (Server Message Block)` when the number of
## :abbr:`DCE-RPC (Distributed Computing Environment/Remote Procedure Calls)`
## analyzers exceeds :zeek:see:`SMB::max_dce_rpc_analyzers`.
## Occurrence of this event may indicate traffic loss, traffic load-balancing
## issues or abnormal SMB protocol usage.
##
## c: The connection.
##
event smb_discarded_dce_rpc_analyzers%(c: connection%);

View file

@ -10,7 +10,7 @@ refine connection SMB_Conn += {
%cleanup{
// Iterate all of the analyzers and destroy them.
for ( auto kv : fid_to_analyzer_map )
for ( const auto& kv : fid_to_analyzer_map )
{
if ( kv.second )
{
@ -49,6 +49,22 @@ refine connection SMB_Conn += {
if ( it == fid_to_analyzer_map.end() )
{
// Too many analyzers?
if ( zeek::BifConst::SMB::max_dce_rpc_analyzers > 0 &&
fid_to_analyzer_map.size() >= zeek::BifConst::SMB::max_dce_rpc_analyzers )
{
if ( smb_discarded_dce_rpc_analyzers )
zeek::BifEvent::enqueue_smb_discarded_dce_rpc_analyzers(zeek_analyzer(), zeek_analyzer()->Conn());
for ( const auto& kv : fid_to_analyzer_map )
{
kv.second->Done();
delete kv.second;
}
fid_to_analyzer_map.clear();
}
auto tmp_analyzer = zeek::analyzer_mgr->InstantiateAnalyzer("DCE_RPC", zeek_analyzer()->Conn());
pipe_dcerpc = static_cast<zeek::analyzer::dce_rpc::DCE_RPC_Analyzer *>(tmp_analyzer);
@ -68,4 +84,19 @@ refine connection SMB_Conn += {
return true;
%}
function forward_dce_rpc_close(fid: uint64): bool
%{
auto it = fid_to_analyzer_map.find(fid);
if ( it != fid_to_analyzer_map.end() )
{
it->second->Done();
delete it->second;
fid_to_analyzer_map.erase(it);
return true;
}
return false;
%}
};

View file

@ -46,7 +46,9 @@ type SMB2_close_request(header: SMB2_Header) = record {
reserved : uint32;
file_id : SMB2_guid;
} &let {
fid: uint64 = file_id.persistent + file_id._volatile;
proc: bool = $context.connection.proc_smb2_close_request(header, this);
maybe_pipe_close: bool = $context.connection.forward_dce_rpc_close(fid);
};
type SMB2_close_response(header: SMB2_Header) = record {