[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [patch] SIGSEGV when adding Connector to Event
[Thread Prev] | [Thread Next]
- Subject: Re: [patch] SIGSEGV when adding Connector to Event
- From: g4-lisz@xxxxxxxxxxxx
- Reply-to: libssh@xxxxxxxxxx
- Date: Wed, 30 Jan 2019 15:33:12 +0100
- To: libssh@xxxxxxxxxx
- Cc: Andreas Schneider <asn@xxxxxxxxxxxxxx>
Hi all, You finally find the test code attached. It's a bit complicated, but it can reproduce the issue: 1. Connect to an ssh server which supports direct socket tunnelling: > ./multichannelfw root myserver.com 22 This opens a local listening port at 20021 2. Open two or more simultaneous ssh sessions via tunnel: > ssh root@localhost -p20021 3. Close one session 4. Open a new one (will stall) 5. Trigger some traffic by typing something in an already open session* 6. Segmentation fault * This is also affected by my other patch "ssh_handle_packets_termination() ignores timeout=0". With this patch, there's no need to trigger traffic to unblock receving messages) Regards, Till On 28.01.19 23:10, g4-lisz@xxxxxxxxxxxx wrote: > > Hi all > > There's a bug in the connector API when subsequently adding to and > removing connectors from an event loop. > > Here's some dummy code to reproduce it (I will add real code later): > > event = ssh_event_new(); > > /* ADD FIRST connector pair (in/out) */ > ssh_connector in1 = ssh_connector_new(session); > ssh_connector_set_out_channel(...); > ssh_connector_set_in_fd(...); > ssh_event_add_connector(event, in1); > > > ssh_connector out1 = ssh_connector_new(session); > ssh_connector_set_out_fd(...); > .... > > ssh_event_dopoll(event, 2000); > > /* ADD SECOND connector pair */ > ssh_connector in2 = ssh_connector_new(session); > ssh_connector_set_out_channel(...); > ssh_connector_set_in_fd(...); > ssh_event_add_connector(event, in2); > > > ssh_connector out2 = ssh_connector_new(session); > ssh_connector_set_out_fd(...); > .... > > ssh_event_dopoll(event, 2000); > > .... /* work done with connector pair 1 */ > > /* REMOVE FIRST connector pair */ > ssh_event_remove_connector(event, in1); > ssh_event_remove_connector(event, out1); > ssh_connector_free(in1); > ssh_connector_free(in1); > .... > > ssh_event_dopoll(event, 2000); > > /* ADD THIRD connector pair */ > ssh_connector in3 = ssh_connector_new(session); > ssh_connector_set_out_channel(...); > ssh_connector_set_in_fd(...); > ssh_event_add_connector(event, in3); > ^^^^ ***SIGSEGV **** > > The underlying problem is ssh_connector_remove_event() where > connector->in_channel and ->out_channel is NULLed. > > The subsequent call to ssh_connector_free() does not remove the > in_channel and out_channel callbacks, because in_ and out_channel is NULL. > So we have some sticky callbacks attached to the channel, and > ssh_connector_remove_event() has removed the poll objects... > > Later on, when adding a new connector, ssh_channel_poll_timeout() is > called at a certain point, which calls the CB function with the > polling objects removed. > > And voila: > > Thread 3 "sshfwd" received signal SIGSEGV, Segmentation fault. > [Switching to Thread 0x7ffff4574700 (LWP 15333)] > 0x00007ffff7b76a04 in ssh_poll_get_events (p=0x0) at /home/till/libssh/src/poll.c:403 > 403 return p->events; > (gdb) backtrace > #0 0x00007ffff7b76a04 in ssh_poll_get_events (p=0x0) at /home/till/libssh/src/poll.c:403 > #1 0x00007ffff7b76ac3 in ssh_poll_add_events (p=0x0, events=4) at /home/till/libssh/src/poll.c:443 > #2 0x00007ffff7b56cd1 in ssh_connector_reset_pollevents (connector=0x7fffec0045b0) at /home/till/libssh/src/connector.c:235 > #3 0x00007ffff7b5757a in ssh_connector_channel_data_cb (session=0x7fffec0008c0, channel=0x7fffec006cc0, data=0x7fffec009940, len=120, > is_stderr=0, userdata=0x7fffec0045b0) at /home/till/libssh/src/connector.c:489 > #4 0x00007ffff7b4cf50 in channel_rcv_data (session=0x7fffec0008c0, type=94 '^', packet=0x7fffec001450, user=0x7fffec0008c0) > at /home/till/libssh/src/channels.c:563 > #5 0x00007ffff7b6dfb5 in ssh_packet_process (session=0x7fffec0008c0, type=94 '^') at /home/till/libssh/src/packet.c:1421 > #6 0x00007ffff7b6d9aa in ssh_packet_socket_callback (data=0x7fffec008d40, receivedlen=176, user=0x7fffec0008c0) > at /home/till/libssh/src/packet.c:1275 > #7 0x00007ffff7b7ba1d in ssh_socket_pollcallback (p=0x7fffec004cc0, fd=6, revents=1, v_s=0x7fffec001280) > at /home/till/libssh/src/socket.c:302 > #8 0x00007ffff7b771ad in ssh_poll_ctx_dopoll (ctx=0x7fffec006900, timeout=-1) at /home/till/libssh/src/poll.c:702 > #9 0x00007ffff7b789eb in ssh_handle_packets (session=0x7fffec0008c0, timeout=-1) at /home/till/libssh/src/session.c:643 > #10 0x00007ffff7b78af1 in ssh_handle_packets_termination (session=0x7fffec0008c0, timeout=0, > fct=0x7ffff7b502c1 <ssh_channel_read_termination>, user=0x7ffff456fb40) at /home/till/libssh/src/session.c:711 > #11 0x00007ffff7b50867 in ssh_channel_poll_timeout (channel=0x7fffec006cc0, timeout=0, is_stderr=0) > at /home/till/libssh/src/channels.c:2974 > #12 0x00007ffff7b5798c in ssh_connector_set_event (connector=0x7fffec007bb0, event=0x7fffec0047f0) at /home/till/libssh/src/connector.c:607 > #13 0x00007ffff7b7762d in ssh_event_add_connector (event=0x7fffec0047f0, connector=0x7fffec007bb0) at /home/till/libssh/src/poll.c:937 > > The big fix is qute simple: Dont' NULL the channel pointers in > ssh_connector_remove_event(). I don't really see the point why they > have to be NULLed there. The event should be removed from the > connector, and not the channels... > > Patch attached. > > Cheers, > Till >
/* gcc -o sshfwd fwtable.c cfglist.c sshfwd.c -lssh -lssh_threads -lpthread */ #include <libssh/libssh.h> #include <libssh/callbacks.h> #include <sys/socket.h> #include <netinet/in.h> #include <linux/netfilter_ipv4.h> #include <arpa/inet.h> #include <sys/time.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define NONBLOCK_WORKAROUND #define MAX_FORWARDS 16 static const char *local_listenip = "127.0.0.1"; static const int local_listenport = 20021; const char *remote_listenhost = "127.0.0.1"; /* resolved by the remote server */ int remote_listenport = 22; static int verbosity = SSH_LOG_PROTOCOL; void add_connector(ssh_session session, ssh_event event, ssh_connector *in, ssh_connector *out, ssh_connector *err, ssh_channel channel, int fd) { #ifdef NONBLOCK_WORKAROUND int blocking = ssh_is_blocking(session); ssh_set_blocking(session, 0); #endif /* stdinh */ (*in) = ssh_connector_new(session); ssh_connector_set_out_channel((*in), channel, SSH_CONNECTOR_STDOUT); ssh_connector_set_in_fd((*in), fd); ssh_event_add_connector(event, (*in)); /* stdout */ (*out) = ssh_connector_new(session); ssh_connector_set_out_fd((*out), fd); ssh_connector_set_in_channel((*out), channel, SSH_CONNECTOR_STDOUT); ssh_event_add_connector(event, (*out)); /* stderr */ (*err) = ssh_connector_new(session); ssh_connector_set_out_fd((*err), fileno(stderr)); ssh_connector_set_in_channel((*err), channel, SSH_CONNECTOR_STDERR); ssh_event_add_connector(event, (*err)); #ifdef NONBLOCK_WORKAROUND ssh_set_blocking(session, blocking); #endif } static void remove_connector(ssh_event event, ssh_connector in, ssh_connector out, ssh_connector err) { ssh_event_remove_connector(event, in); ssh_event_remove_connector(event, out); ssh_event_remove_connector(event, err); ssh_connector_free(in); ssh_connector_free(out); ssh_connector_free(err); } int listen_poll(int listensock, int timout) { int forwardsock = -1; int max_fd, rc; unsigned sout_len; fd_set fds; struct timeval tv; _ssh_log(SSH_LOG_PROTOCOL, "listen_poll", "Listening for connection on port %d", local_listenport); while (1) { FD_ZERO(&fds); FD_SET(listensock, &fds); max_fd = listensock + 1; tv.tv_sec = 0; tv.tv_usec = timout; sout_len = 0; rc = select(max_fd, &fds, NULL, NULL, &tv); if (-1 == rc && EINTR == errno) { continue; } else if (rc > 0) { if (FD_ISSET(listensock, &fds)) { _ssh_log(SSH_LOG_PROTOCOL, "main", "FD_ISSET"); forwardsock = accept(listensock, NULL, &sout_len); if (forwardsock < 0) { _ssh_log(SSH_LOG_WARNING, "main", "accept failed: %s", strerror(errno)); } else { _ssh_log(SSH_LOG_WARNING, "main", "Local client connected."); } } } else if (0 > rc) { _ssh_log(SSH_LOG_WARNING, "main", "select failed: %s", strerror(errno)); } return forwardsock; } } int forward_tunnel(ssh_session session, int listensocket) { int i, rc, channel_currid = -1, channel_count = 0, channel_count_active = 0, has_closed=0; ssh_connector connector_in[MAX_FORWARDS], connector_out[MAX_FORWARDS], connector_err[MAX_FORWARDS]; ssh_channel channel[MAX_FORWARDS]; int forwardsock, socket[MAX_FORWARDS]; _ssh_log(SSH_LOG_FUNCTIONS, "forward_tunnel", "New session"); ssh_event event = ssh_event_new(); ssh_event_add_session(event, session); do { forwardsock = listen_poll(listensocket, 10); if (forwardsock > -1) { /* New channel has been added */ channel_count++; channel_currid = channel_count - 1; channel[channel_currid] = ssh_channel_new(session); if (channel[channel_currid] == NULL) { _ssh_log(SSH_LOG_WARNING, "forward_tunnel", "Couldn't allocate channel: %s", ssh_get_error(session)); close(forwardsock); continue; } _ssh_log(SSH_LOG_PROTOCOL, "forward_tunnel", "Open channel to: %s:%d", remote_listenhost, remote_listenport); rc = ssh_channel_open_forward(channel[channel_currid], remote_listenhost, remote_listenport, local_listenip, local_listenport); if (rc != SSH_OK) { _ssh_log(SSH_LOG_WARNING, "forward_tunnel", "Couldn't open forwarding channel %d: %s", rc, ssh_get_error(session)); close(forwardsock); ssh_channel_free(channel[channel_currid]); channel[channel_currid] = NULL; continue; } socket[channel_currid] = forwardsock; _ssh_log(SSH_LOG_FUNCTIONS, "forward_tunnel", "Create Connector for Channel Id#%d", channel_currid); add_connector(session, event, &connector_in[channel_currid], &connector_out[channel_currid], &connector_err[channel_currid], channel[channel_currid], socket[channel_currid]); channel_count_active++; } rc = ssh_event_dopoll(event, 0); //_ssh_log(SSH_LOG_FUNCTIONS, "forward_tunnel", "ssh_event_dopoll ret %d", rc); //_ssh_log(SSH_LOG_PROTOCOL, "forward_tunnel", "channel_count_active: %d, channel_currid: %d, channel_count: %d", // channel_count_active, channel_currid, channel_count); has_closed = 0; for (i=0; i <= channel_currid; i++) { if(channel[i] && ssh_channel_is_eof(channel[i])) { ssh_channel_send_eof(channel[i]); } if(channel[i] && ssh_channel_is_closed(channel[i])) { remove_connector(event, connector_in[i], connector_out[i], connector_err[i]); close(socket[i]); channel[i] = NULL; has_closed = 1; channel_count_active--; _ssh_log(SSH_LOG_PROTOCOL, "forward_tunnel", "Channel Id#%d closed. Remaining %d channels.", i, channel_count_active); } } if (has_closed) { for (i=0; i <= channel_currid; i++) { if (channel[i]) { remove_connector(event, connector_in[i], connector_out[i], connector_err[i]); } } for (i=0; i <= channel_currid; i++) { if (channel[i]) { add_connector(session, event, &connector_in[i], &connector_out[i], &connector_err[i], channel[i], socket[i]); } } ssh_event_add_session(event, session); } } while (channel_count == 0 || channel_count_active > 0); ssh_event_free(event); return rc; } ssh_session create_session(const char *host, int port, const char *uname) { ssh_session session; int rc; session = ssh_new(); if (session == NULL) { _ssh_log(SSH_LOG_WARNING, "create_session", "ssh_new failed."); return NULL; } ssh_options_set(session, SSH_OPTIONS_HOST, host); ssh_options_set(session, SSH_OPTIONS_USER, uname); ssh_options_set(session, SSH_OPTIONS_PORT, &port); rc = ssh_connect(session); if (rc != SSH_OK) { _ssh_log(SSH_LOG_WARNING, "create_session", "Error connecting to host: %s", ssh_get_error(session)); ssh_free(session); return NULL; } _ssh_log(SSH_LOG_PROTOCOL, "create_session", "We are connected."); rc = ssh_userauth_publickey_auto(session, NULL, NULL); if (rc != SSH_AUTH_SUCCESS) { _ssh_log(SSH_LOG_WARNING, "create_session", "Authentication failed: %s", ssh_get_error(session)); ssh_disconnect(session); ssh_free(session); return NULL; } _ssh_log(SSH_LOG_PROTOCOL, "create_session", "We are authenticated."); return session; } void main_loop(int listensocket, const char *host, int port, const char *uname) { ssh_session session = create_session(host, port, uname); if (session == NULL) { return; } forward_tunnel(session, listensocket); ssh_disconnect(session); ssh_free(session); _ssh_log(SSH_LOG_PROTOCOL, "main_loop", "Ended."); } int main(int argc, char **argv) { struct sockaddr_in sin; int listensock = -1, dest_port; if (argc < 4) { printf("Usage: %s <SSH_User> <SSH_Host> <SSH_Port>\n", argv[0]); return 0; } dest_port = atoi(argv[3]); ssh_set_log_level(verbosity); ssh_init(); listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (listensock == -1) { _ssh_log(SSH_LOG_WARNING, "main", "Error creating socket: %s", strerror(errno)); goto shutdown; } int optval = 1; if (-1 == setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int))) { _ssh_log(SSH_LOG_WARNING, "main", "Set SO_REUSEADDR failed: %s", strerror(errno)); } sin.sin_family = AF_INET; sin.sin_port = htons(local_listenport); sin.sin_addr.s_addr = htonl(INADDR_ANY); if (-1 == bind(listensock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in))) { _ssh_log(SSH_LOG_WARNING, "main", "Bind failed: %s", strerror(errno)); goto shutdown; } if (-1 == listen(listensock, 1)) { _ssh_log(SSH_LOG_WARNING, "main", "Listen failed: %s", strerror(errno)); goto shutdown; } main_loop(listensock, argv[2], dest_port, argv[1]); shutdown: close(listensock); printf("END\n"); return 0; }
[patch] SIGSEGV when adding Connector to Event | g4-lisz@xxxxxxxxxxxx |