Skip to content

Commit 6fcc864

Browse files
author
HD Moore
committed
Reduce the chance of file descriptor leaks in SMBServer
This patch addresses three observed error conditions in long-running SMB services. 1. A call to get_once() in on_client_data could raise a Timeout exception and bubble all the way up to the dispatcher. This should technically never happen, but gets triggered for zero-byte writes and clients closing their connections. The fix was to handle the exception and lower the timeout. The change was tested with a number of SMB clients to make sure this didn't introduce any regressions. 2. A client could indefinitely keep a connection to the SMB server. The SMB server now disconnects idle clients after 120 seconds of inactivity (configurable). 3. A client could send a large amount of data that was invalid SMB traffic, using up memory as a potential DoS. Caveats: The idle client sweep occurs every 100 requests or at an interval equal to the idle timeout. A client could fill up the entire connection table on its own, preventing the sweep from occurring by preventing new connections. Fixing this would require a dedicated thread to sweep for idle connections and is a more aggressive attack than this patch is designed to defend against (accidental connection flooding, basically).
1 parent 1cdf1c2 commit 6fcc864

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

lib/msf/core/exploit/smb.rb

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,16 @@ def initialize(info = {})
698698
[
699699
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ])
700700
], self.class)
701+
702+
register_advanced_options(
703+
[
704+
OptInt.new('SMBServerMaximumBuffer', [ true, "The maximum number of data in megabytes to buffer", 2 ]),
705+
OptInt.new('SMBServerIdleTimeout', [ true, "The maximum amount of time to keep an idle session open in seconds", 120 ]),
706+
], self.class)
707+
708+
@smb_server_last_pool_sweep = Time.now.to_f
709+
@smb_server_pool_mutex = Mutex.new
710+
@smb_server_request_counter = 0
701711
end
702712

703713
def setup
@@ -722,16 +732,40 @@ def on_client_close(client)
722732

723733
def smb_conn(c)
724734
@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport}
735+
smb_pool_update(c)
725736
end
726737

727738
def smb_stop(c)
739+
# Make sure the socket is closed
740+
c.close rescue nil
741+
# Delete the state table entry
728742
@state.delete(c)
729743
end
730744

731745
def smb_recv(c)
732746
smb = @state[c]
733747
smb[:data] ||= ''
734-
smb[:data] << c.get_once
748+
749+
# Capture any low-level timeout exceptions to prevent it from bubbling
750+
buff = c.get_once(-1, 0.25) rescue nil
751+
752+
# The client said it had data, but lied, kill the session
753+
unless buff and buff.length > 0
754+
smb_stop(c)
755+
return
756+
end
757+
758+
# Append the new data to the buffer
759+
smb[:data] << buff
760+
761+
# Prevent a simplistic DoS if the buffer is too big
762+
if smb[:data].length > (1024*1024*datastore['SMBServerMaximumBuffer'])
763+
smb_stop(c)
764+
return
765+
end
766+
767+
# Update the last-seen timestamp and purge old entries
768+
smb_pool_update(c)
735769

736770
while(smb[:data].length > 0)
737771

@@ -821,6 +855,34 @@ def smb_error(cmd, c, errorclass, esn = false)
821855
c.put(pkt.to_s)
822856
end
823857

858+
# Update the last-seen timestamp and purge old entries
859+
def smb_pool_update(c)
860+
861+
@state[c][:last_action] = Time.now.to_f
862+
@smb_server_request_counter += 1
863+
864+
unless @smb_server_request_counter % 100 == 0 ||
865+
@smb_server_last_pool_sweep + smb_idle_timeout < Time.now.to_f
866+
return
867+
end
868+
869+
# Synchronize pool sweeps in case we move to threaded services
870+
@smb_server_pool_mutex.synchronize do
871+
purge_list = []
872+
873+
@smb_server_last_pool_sweep = Time.now.to_f
874+
875+
@state.keys.each do |sc|
876+
if @state[sc][:last_action] + datastore['SMBServerIdleTimeout'].to_f < Time.now.to_f
877+
purge_list << sc
878+
end
879+
end
880+
881+
# Purge any idle connections to rescue file descriptors
882+
purge_list.each { |sc| smb_stop(sc) }
883+
end
884+
end
885+
824886
end
825887

826888

0 commit comments

Comments
 (0)