Skip to content

Commit

Permalink
Fix hanging forever if rsock doesnt connect
Browse files Browse the repository at this point in the history
  • Loading branch information
adfoster-r7 committed Sep 13, 2023
1 parent 86d1278 commit 647636a
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 12 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
- '3.2'
os:
- ubuntu-20.04
- windows-2019
- macos-11
- ubuntu-latest
exclude:
- { os: ubuntu-latest, ruby: '2.7' }
Expand All @@ -52,7 +54,7 @@ jobs:
name: ${{ matrix.os }} - Ruby ${{ matrix.ruby }} - ${{ matrix.test_cmd }}
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand Down
40 changes: 29 additions & 11 deletions lib/rex/socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -672,20 +672,27 @@ def self.ipv6_mac(intf)
# is no concurrent use of the same locals and this is safe.
def self.tcp_socket_pair
lsock = nil
last_child_error = nil
accept_timeout = 10
rsock = nil
laddr = '127.0.0.1'
lport = 0
threads = []
mutex = ::Mutex.new

threads << Rex::ThreadFactory.spawn('TcpSocketPair', false) {
threads << Rex::ThreadFactory.spawn('TcpSocketPair', false) do
server = nil
mutex.synchronize {
threads << Rex::ThreadFactory.spawn('TcpSocketPairClient', false) {
mutex.synchronize {
rsock = ::TCPSocket.new( laddr, lport )
}
}
mutex.synchronize do
threads << Rex::ThreadFactory.spawn('TcpSocketPairClient', false) do
mutex.synchronize do
begin
rsock = ::TCPSocket.new( laddr, lport )
rescue => e
last_child_error = "#{e.class} - #{e.message}"
raise
end
end
end
server = ::TCPServer.new(laddr, 0)
if (server.getsockname =~ /127\.0\.0\.1:/)
# JRuby ridiculousness
Expand All @@ -697,12 +704,23 @@ def self.tcp_socket_pair
# sockaddr
lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname )
end
}
lsock, _ = server.accept
end

readable, _writable, _errors = ::IO.select([server], nil, nil, accept_timeout)
if readable && readable.any?
lsock, _ = server.accept
else
raise RuntimeError, "rsock didn't connect in #{accept_timeout} seconds"
end

server.close
}
end

threads.each { |t| t.join }
threads.each do |thread|
thread.join
rescue => e
raise "Error #{e} - last child error: #{last_child_error}"
end

return [lsock, rsock]
end
Expand Down
38 changes: 38 additions & 0 deletions spec/rex/socket_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@

RSpec.describe Rex::Socket do

describe '.tcp_socket_pair' do
let(:mock_thread_factory) do
double :mock_thread_factory
end

before(:each) do
stub_const('Rex::ThreadFactory', mock_thread_factory)
# Fallback implementation from https://github.com/rapid7/metasploit-framework/blob/30e66c43a4932df922d7f1d986fb98387bd0ab1a/lib/rex/thread_factory.rb#L27-L37
allow(mock_thread_factory).to receive(:spawn) do |name, crit, *args, &block|
if block
t = ::Thread.new(*args){ |*args_copy| block.call(*args_copy) }
else
t = ::Thread.new(*args)
end
t[:tm_name] = name
t[:tm_crit] = crit
t[:tm_time] = ::Time.now
t[:tm_call] = caller
t
end
end

it 'creates two socket pairs' do
lsock, rsock = described_class.tcp_socket_pair
lsock.extend(Rex::IO::Stream)
rsock.extend(Rex::IO::Stream)

expect(lsock.closed?).to be(false)
expect(rsock.closed?).to be(false)

lsock.close
rsock.close

expect(lsock.closed?).to be(true)
expect(rsock.closed?).to be(true)
end
end

describe '.addr_itoa' do

context 'with explicit v6' do
Expand Down

0 comments on commit 647636a

Please sign in to comment.