ParallelSSH/parallel-ssh

Segfaults when running copy_remote_file on proxy_client

maxballenger opened this issue · 2 comments

For general questions please use the mail group.

Describe the bug
Hi @pkittenis , following up on #292 as I think I encountered a similar issue. I am trying to connect to a single host behind a proxy and do put/get files and run commands on that host. I am also trying to do put/get files and run commands on the proxy itself. I had been doing this with two separate SSHClient() objects as shown in #292. I refactored my code to only use a single SSHClient object and to talk to the proxy using its _proxy_client attribute. However, this is not working for me. I am getting frequent segfaults. I've attached a test to reproduce.

To Reproduce

Steps to reproduce the behavior:

  1. Fill in the constants in the attached test with appropriate IP addresses, username, and paths to keys
  2. Run the test
  3. Typically "test_2_many_getfile_host" will proceed just fine, but "test_2_many_getfile_proxy" will not make it through 20 iterations, regardless of whether it runs before or after the host test.
  4. I get many different error messages, some of which are shown below.
  • python3: /libssh2-1.9.0.tar.gz/libssh2-libssh2-1.9.0/src/transport.c:349: _libssh2_transport_read: Assertion `remainbuf >= 0' failed.
  • Silent python exit but dmesg indicates something like the following (not always in libcrypto) "python3[12794]: segfault at 1cdb000 ip 00007f7006cae3cd sp 00007f70025445c0 error 4 in libcrypto-ab1baf6c.so.1.1[7f7006ad2000+2b0000]"

Here is a GDB backtrace I got from running the test under GDB when it exited with the first message above about remainbuf:

Thread 1 "python3" received signal SIGABRT, Aborted.
0x00007f7e1b3b5fb7 in raise () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007f7e1b3b5fb7 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f7e1b3b7921 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f7e1b3a748a in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007f7e1b3a7502 in __assert_fail ()
   from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007f7e1883e677 in _libssh2_transport_read ()
   from /usr/lib/python3.6/site-packages/ssh2/../ssh2_python.libs/libssh2-55f304ca.so.1.0.1
#5  0x00007f7e18817dd3 in _libssh2_channel_write ()
   from /usr/lib/python3.6/site-packages/ssh2/../ssh2_python.libs/libssh2-55f304ca.so.1.0.1
#6  0x00007f7e18838b6e in sftp_open ()
   from /usr/lib/python3.6/site-packages/ssh2/../ssh2_python.libs/libssh2-55f304ca.so.1.0.1
#7  0x00007f7e18839157 in libssh2_sftp_open_ex ()
   from /usr/lib/python3.6/site-packages/ssh2/../ssh2_python.libs/libssh2-55f304ca.so.1.0.1
#8  0x00007f7e16fcac1a in __pyx_pf_4ssh2_4sftp_4SFTP_8open (
    __pyx_v_mode=256, __pyx_v_flags=1, __pyx_v_filename=<optimized out>, 
    __pyx_v_self=0x7f7e15279be0) at ssh2/sftp.c:2381
#9  __pyx_pw_4ssh2_4sftp_4SFTP_9open (__pyx_v_self=0x7f7e15279be0, 
    __pyx_args=<optimized out>, __pyx_kwds=<optimized out>)

Expected behavior
Able to disconnect/reconnect from proxy and still get files

Actual behaviour
Segfaults when I try to disconnect/reconnect from proxy and get files

Screenshots
If applicable, add screenshots to help explain your problem.

Additional information
ssh2_python==0.22.0
parallel-ssh==2.4.5

Code to Reproduce

import unittest
import time
from pssh.clients import SSHClient


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"
TEST_FILE = "test_file.txt"

RECONNECT_DELAY = 1
NUMBER_RECONNECTS = 20


class TestB1Sensor(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.ssh = HostAndProxy(PROXY_IP, HOST_IP, PROXY_USER, HOST_USER, PROXY_KEY, HOST_KEY)

    def get_file(self, host):
        host.get_file(TEST_FILE, TEST_FILE)
        with open(TEST_FILE, 'r') as f:
            print(f.read())

    def test_1_many_getfile_proxy(self):
        print("Testing getfile on proxy")
        for i in range(NUMBER_RECONNECTS):
            print(f"Running cycle {i+1}/{NUMBER_RECONNECTS}")
            print("Closing ssh connection")
            self.ssh.disconnect()
            time.sleep(RECONNECT_DELAY)
            self.ssh.connect()
            print("Connection re-established")
            self.get_file(self.ssh.proxy)

    def test_2_many_getfile_host(self):
        print("Testing getfile on host")
        for i in range(NUMBER_RECONNECTS):
            print(f"Running cycle {i}/{NUMBER_RECONNECTS}")
            print("Closing ssh connection")
            self.ssh.disconnect()
            time.sleep(RECONNECT_DELAY)
            self.ssh.connect()
            print("Connection re-established")
            self.get_file(self.ssh.host)


class HostAndProxy:
    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):
        self.host = Host(self.ip_proxy, self.ip_host,
                         self.user_proxy, self.user_host,
                         self.key_proxy, self.key_host)
        self.proxy = Proxy(self.host.get_proxy_client())

    def disconnect(self):
        self.host.disconnect()


class SSHTarget:
    def get_file(self, remotepath, localpath, recurse: bool = False):
        self.ssh.copy_remote_file(remotepath, localpath, recurse=recurse)


class Host(SSHTarget):
    def __init__(self, ip_proxy, ip_host, user_proxy, user_host, key_proxy, key_host):
        # Configure IP
        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()

    def get_proxy_client(self) -> SSHClient:
        return self.ssh._proxy_client


class Proxy(SSHTarget):
    def __init__(self, proxy_client):
        self.ssh = proxy_client


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

OK, after poking through the library code a little more, using the _proxy_client attribute the way I am seems like a bad idea. However, if I don't do it this way, then I am back in the land of creating two SSHClient() objects, one for the end host through and one for the proxy itself, if I want to do SSH ops on both the proxy and the endpoint host. This also creates segfaults.

Closing this as I've narrowed it down and this issue report has a lot of irrelevant information in it