diff --git a/src/session.c b/src/session.c index 2d1b042b..3688dd4a 100644 --- a/src/session.c +++ b/src/session.c @@ -816,12 +816,18 @@ nc_session_free_transport(struct nc_session *session, int *multisession) free(siter); } while (session->ti.libssh.next != session); } - /* remember sock so we can close it */ - sock = ssh_get_fd(session->ti.libssh.session); if (connected) { - /* does not close sock */ + /* remember sock so we can close it */ + sock = ssh_get_fd(session->ti.libssh.session); + + /* clears sock but does not close it if passed via options (libssh >= 0.10) */ ssh_disconnect(session->ti.libssh.session); +#if (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR < 10) + sock = -1; +#endif } + + /* closes sock if set */ ssh_free(session->ti.libssh.session); } else { /* remove the session from the list */ diff --git a/src/session_p.h b/src/session_p.h index 317ab4c5..d62cee06 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -1274,7 +1274,7 @@ struct nc_session *nc_accept_callhome_ssh_sock(int sock, const char *host, uint1 * @brief Establish SSH transport on a socket. * * @param[in] session Session structure of the new connection. - * @param[in] sock Socket of the new connection. + * @param[in] sock Socket of the new connection, closed if not set to the session. * @param[in] timeout Transport operations timeout in msec (not SSH authentication one). * @return 1 on success, 0 on timeout, -1 on error. */ diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index 36527077..d798f986 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -2020,8 +2020,25 @@ nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opt if (ssh_bind_accept_fd(sbind, session->ti.libssh.session, sock) == SSH_ERROR) { ERR(session, "SSH failed to accept a new connection (%s).", ssh_get_error(sbind)); rc = -1; + + /* Avoid closing the socket on failure to prevent a possible double close. + * On failure, sock may or not be set to the session. In theory, we should + * be able to compare sock with ssh_get_fd() and close it only if it was + * not set, for example: + * + * if (ssh_get_fd(session) == sock) + * sock = -1; + * + * However, if ssh_bind_accept_fd() fails to allocate the socket structure + * internally, calling ssh_get_fd() will dereference a NULL pointer due to + * a buggy behavior in libssh. + */ + sock = -1; goto cleanup; } + + /* use SSH_OPTIONS_FD so libssh won't close the socket in ssh_disconnect() */ + ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock); sock = -1; /* set to non-blocking */