Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dangling channel reference in TunnelServer #304

Closed
maxballenger opened this issue Jul 6, 2021 · 0 comments
Closed

Dangling channel reference in TunnelServer #304

maxballenger opened this issue Jul 6, 2021 · 0 comments
Labels

Comments

@maxballenger
Copy link
Contributor

maxballenger commented Jul 6, 2021

For general questions please use the mail group.

Describe the bug
TunnelServer method _wait_send_receive_lets() calls self._client.close_channel(). The client reference that ends up here is a _proxy_client attribute of an SSHClient object connecting to a remote host through a proxy. When disconnect() is called on this SSHClient object, its proxy_client is also disconnected, which eventually triggers the return of the joinall() call inside _wait_send_receive_lets(). By the time this happens, the _proxy_client's session has likely been freed, which also freed the channel, so the call to close_channel() causes a double free.

This can take a while to happen because LocalForwarder only cleans up the servers once every 60 seconds and I think generational garbage collection must also be triggered in order for the segfault to show up.

I believe this can be fixed by moving the call to _proxy_client.disconnect() into _wait_send_receive_lets().

To Reproduce

Steps to reproduce the behavior:

  1. Run the code below
  2. You should see a segfault
import unittest
import time
from pssh.clients.native.single import SSHClient
from pssh.clients.native.tunnel import FORWARDER
import gc


PROXY_IP = '10.1.15.1'
HOST_IP = '172.16.0.101'
PROXY_USER = 'user'
HOST_USER = 'user'
PROXY_KEY = "/path/to/proxy/key"
HOST_KEY = "/path/to/host/key"

GC_DELAY = 1
NUMBER_RECONNECTS = 20


class TestProxiedHost(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.phost = ProxiedHost(PROXY_IP, HOST_IP, PROXY_USER, HOST_USER, PROXY_KEY, HOST_KEY)

    def test_many_reconnects(self):
        for i in range(NUMBER_RECONNECTS):
            print(f"Running cycle {i+1}/{NUMBER_RECONNECTS}")
            print("Closing ssh connection")
            self.phost.disconnect()
            time.sleep(GC_DELAY)
            gc.collect()    # This may speed up the segfault
            self.phost.connect()
            print("Connection re-established")


class ProxiedHost:
    def __init__(self, ip_proxy, ip_host, user_proxy, user_host, key_proxy, key_host):
        self.ip_proxy = ip_proxy
        self.ip_host = ip_host
        self.user_proxy = user_proxy
        self.user_host = user_host
        self.key_proxy = key_proxy
        self.key_host = key_host

        self.connect()

    def connect(self):
        kwargs = {
            'host': str(self.ip_host),
            'user': self.user_host,
            'pkey': self.key_host,
            'proxy_host': str(self.ip_proxy),
            'proxy_user': self.user_proxy,
            'proxy_pkey': self.key_proxy
        }
        self.ssh = SSHClient(**kwargs)

    def disconnect(self):
        self.ssh.disconnect()
        FORWARDER._cleanup_servers()    # This speeds up the segfault


if __name__ == '__main__':
    unittest.main()

Expected behavior
I expect to be able to connect/disconnect from an SSHClient at will without segfaults

Actual behaviour
segfault

Additional information
Include version of ssh2-python and any other relevant information.

ssh2-python==0.26.0
parallel-ssh==2.5.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants