From 1e85cb4b7b88c2b2a21ff425eee071128106b085 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 31 Oct 2025 13:46:18 +0100 Subject: [PATCH] scp/sftp: fix disconnect When a SCP/SFTP connection calls the protocol handler disconnect, it required the connections *and* the easy handles SSH meta data to be present. When the disconnect is called with an admin handle, the easy meta data is not present, which prevented the shutdown to run. The easy meta data is however not necessary to run the shutdown state machine. Calling it with a NULL `sshp` is fine. To avoid any mixups, check `sshp` in state operations that need it. Fixes #19293 Reported-by: And-yW on github Closes #19295 --- lib/vssh/libssh.c | 61 +++++++++++++++++------- lib/vssh/libssh2.c | 113 ++++++++++++++++++++++++++++++--------------- 2 files changed, 120 insertions(+), 54 deletions(-) diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index df466dac4c..af3767ca71 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -1979,13 +1979,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, rc = myssh_in_SFTP_REALPATH(data, sshc); break; case SSH_SFTP_QUOTE_INIT: - rc = myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_POSTQUOTE_INIT: rc = myssh_in_SFTP_POSTQUOTE_INIT(data, sshc); break; case SSH_SFTP_QUOTE: - rc = myssh_in_SFTP_QUOTE(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_QUOTE(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_NEXT_QUOTE: rc = myssh_in_SFTP_NEXT_QUOTE(data, sshc); @@ -2115,7 +2117,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, case SSH_SFTP_FILETIME: { sftp_attributes attrs; - + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } attrs = sftp_stat(sshc->sftp_session, sshp->path); if(attrs) { data->info.filetime = attrs->mtime; @@ -2129,20 +2134,27 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, case SSH_SFTP_TRANS_INIT: if(data->state.upload) myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT); - else { + else if(sshp) { if(sshp->path[strlen(sshp->path)-1] == '/') myssh_to(data, sshc, SSH_SFTP_READDIR_INIT); else myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } + else + result = CURLE_FAILED_INIT; break; case SSH_SFTP_UPLOAD_INIT: - rc = myssh_in_UPLOAD_INIT(data, sshc, sshp); + rc = sshp ? myssh_in_UPLOAD_INIT(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(sshp->path) > 1) { + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } + else if(strlen(sshp->path) > 1) { sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS); } @@ -2153,7 +2165,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, case SSH_SFTP_CREATE_DIRS: sshc->slash_pos = strchr(sshc->slash_pos, '/'); - if(sshc->slash_pos) { + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } + else if(sshc->slash_pos) { *sshc->slash_pos = 0; infof(data, "Creating directory '%s'", sshp->path); @@ -2165,6 +2181,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, case SSH_SFTP_CREATE_DIRS_MKDIR: /* 'mode' - parameter is preliminary - default to 0644 */ + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } rc = sftp_mkdir(sshc->sftp_session, sshp->path, (mode_t)data->set.new_directory_perms); *sshc->slash_pos = '/'; @@ -2188,10 +2208,12 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, break; case SSH_SFTP_READDIR_INIT: - rc = myssh_in_SFTP_READDIR_INIT(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_READDIR_INIT(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_READDIR: - rc = myssh_in_SFTP_READDIR(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_READDIR(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_READDIR_LINK: rc = myssh_in_SFTP_READDIR_LINK(data, sshc); @@ -2203,19 +2225,25 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, rc = myssh_in_SFTP_READDIR_DONE(data, sshc); break; case SSH_SFTP_DOWNLOAD_INIT: - rc = myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_DOWNLOAD_STAT: rc = myssh_in_SFTP_DOWNLOAD_STAT(data, sshc); break; case SSH_SFTP_CLOSE: - rc = myssh_in_SFTP_CLOSE(data, sshc, sshp); + rc = sshp ? myssh_in_SFTP_CLOSE(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_SHUTDOWN: rc = myssh_in_SFTP_SHUTDOWN(data, sshc); break; case SSH_SCP_TRANS_INIT: + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); if(result) { sshc->actualcode = result; @@ -2252,7 +2280,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, break; case SSH_SCP_UPLOAD_INIT: - + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } rc = ssh_scp_init(sshc->scp_session); if(rc != SSH_OK) { err_msg = ssh_get_error(sshc->ssh_session); @@ -2832,11 +2863,9 @@ static CURLcode scp_disconnect(struct Curl_easy *data, struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void)dead_connection; - if(sshc && sshc->ssh_session && sshp) { + if(sshc && sshc->ssh_session) { /* only if there is a session still around to use! */ - myssh_to(data, sshc, SSH_SESSION_DISCONNECT); - result = myssh_block_statemach(data, sshc, sshp, TRUE); } @@ -3012,7 +3041,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, DEBUGF(infof(data, "SSH DISCONNECT starts now")); - if(sshc && sshc->ssh_session && sshp) { + if(sshc && sshc->ssh_session) { /* only if there is a session still around to use! */ myssh_to(data, sshc, SSH_SFTP_SHUTDOWN); result = myssh_block_statemach(data, sshc, sshp, TRUE); diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 18e3cdb1d5..5990da25bf 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -826,7 +826,7 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, char *errmsg = NULL; int errlen; libssh2_session_last_error(sshc->ssh_session, &errmsg, &errlen, 0); - failf(data, "libssh2: %s", errmsg); + failf(data, "libssh2 method '%s' failed: %s", hostkey_method, errmsg); result = libssh2_session_error_to_CURLE(rc); } } @@ -2721,11 +2721,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_REALPATH: - result = ssh_state_sftp_realpath(data, sshc, sshp); + result = sshp ? ssh_state_sftp_realpath(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_QUOTE_INIT: - result = ssh_state_sftp_quote_init(data, sshc, sshp); + result = sshp ? ssh_state_sftp_quote_init(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_POSTQUOTE_INIT: @@ -2733,7 +2735,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_QUOTE: - result = ssh_state_sftp_quote(data, sshc, sshp); + result = sshp ? ssh_state_sftp_quote(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_NEXT_QUOTE: @@ -2741,11 +2744,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_QUOTE_STAT: - result = ssh_state_sftp_quote_stat(data, sshc, sshp, block); + result = sshp ? ssh_state_sftp_quote_stat(data, sshc, sshp, block) : + CURLE_FAILED_INIT; break; case SSH_SFTP_QUOTE_SETSTAT: - result = ssh_state_sftp_quote_setstat(data, sshc, sshp); + result = sshp ? ssh_state_sftp_quote_setstat(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_QUOTE_SYMLINK: @@ -2784,10 +2789,15 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, case SSH_SFTP_FILETIME: { LIBSSH2_SFTP_ATTRIBUTES attrs; + int rc; - int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, - curlx_uztoui(strlen(sshp->path)), - LIBSSH2_SFTP_STAT, &attrs); + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path, + curlx_uztoui(strlen(sshp->path)), + LIBSSH2_SFTP_STAT, &attrs); if(rc == LIBSSH2_ERROR_EAGAIN) { result = CURLE_AGAIN; break; @@ -2803,16 +2813,19 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, case SSH_SFTP_TRANS_INIT: if(data->state.upload) myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT); - else { + else if(sshp) { if(sshp->path[strlen(sshp->path)-1] == '/') myssh_state(data, sshc, SSH_SFTP_READDIR_INIT); else myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT); } + else + result = CURLE_FAILED_INIT; break; case SSH_SFTP_UPLOAD_INIT: - result = sftp_upload_init(data, sshc, sshp, block); + result = sshp ? sftp_upload_init(data, sshc, sshp, block) : + CURLE_FAILED_INIT; if(result) { myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; @@ -2820,7 +2833,9 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(sshp->path) > 1) { + if(!sshp) + result = CURLE_FAILED_INIT; + else if(strlen(sshp->path) > 1) { sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */ myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS); } @@ -2830,6 +2845,10 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_CREATE_DIRS: + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } sshc->slash_pos = strchr(sshc->slash_pos, '/'); if(sshc->slash_pos) { *sshc->slash_pos = 0; @@ -2842,25 +2861,33 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_CREATE_DIRS_MKDIR: - result = ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp); + result = sshp ? ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_READDIR_INIT: - result = ssh_state_sftp_readdir_init(data, sshc, sshp); + result = sshp ? ssh_state_sftp_readdir_init(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_READDIR: - result = sftp_readdir(data, sshc, sshp, block); + result = sshp ? sftp_readdir(data, sshc, sshp, block) : + CURLE_FAILED_INIT; if(result) { myssh_state(data, sshc, SSH_SFTP_CLOSE); } break; case SSH_SFTP_READDIR_LINK: - result = ssh_state_sftp_readdir_link(data, sshc, sshp); + result = sshp ? ssh_state_sftp_readdir_link(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_READDIR_BOTTOM: + if(!sshp) { + result = CURLE_FAILED_INIT; + break; + } result = curlx_dyn_addn(&sshp->readdir, "\n", 1); if(!result) result = Curl_client_write(data, CLIENTWRITE_BODY, @@ -2890,11 +2917,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_DOWNLOAD_INIT: - result = ssh_state_sftp_download_init(data, sshc, sshp); + result = sshp ? ssh_state_sftp_download_init(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_DOWNLOAD_STAT: - result = sftp_download_stat(data, sshc, sshp, block); + result = sshp ? sftp_download_stat(data, sshc, sshp, block) : + CURLE_FAILED_INIT; if(result) { myssh_state(data, sshc, SSH_SFTP_CLOSE); sshc->nextstate = SSH_NO_STATE; @@ -2902,7 +2931,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SFTP_CLOSE: - result = ssh_state_sftp_close(data, sshc, sshp); + result = sshp ? ssh_state_sftp_close(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SFTP_SHUTDOWN: @@ -2910,7 +2940,8 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SCP_TRANS_INIT: - result = Curl_getworkingpath(data, sshc->homedir, &sshp->path); + result = sshp ? Curl_getworkingpath(data, sshc->homedir, &sshp->path) : + CURLE_FAILED_INIT; if(result) { myssh_state(data, sshc, SSH_STOP); break; @@ -2931,11 +2962,13 @@ static CURLcode ssh_statemachine(struct Curl_easy *data, break; case SSH_SCP_UPLOAD_INIT: - result = ssh_state_scp_upload_init(data, sshc, sshp); + result = sshp ? ssh_state_scp_upload_init(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SCP_DOWNLOAD_INIT: - result = ssh_state_scp_download_init(data, sshc, sshp); + result = sshp ? ssh_state_scp_download_init(data, sshc, sshp) : + CURLE_FAILED_INIT; break; case SSH_SCP_DONE: @@ -3559,9 +3592,6 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, if(sshc->ssh_agent) { rc = libssh2_agent_disconnect(sshc->ssh_agent); - if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) - return CURLE_AGAIN; - if((rc < 0) && data) { char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, @@ -3569,6 +3599,9 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, infof(data, "Failed to disconnect from libssh2 agent: %d %s", rc, err_msg); } + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + libssh2_agent_free(sshc->ssh_agent); sshc->ssh_agent = NULL; @@ -3580,23 +3613,20 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, if(sshc->sftp_handle) { rc = libssh2_sftp_close(sshc->sftp_handle); - if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) - return CURLE_AGAIN; - if((rc < 0) && data) { char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg); } + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + sshc->sftp_handle = NULL; } if(sshc->ssh_channel) { rc = libssh2_channel_free(sshc->ssh_channel); - if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) - return CURLE_AGAIN; - if((rc < 0) && data) { char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, @@ -3604,30 +3634,37 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, infof(data, "Failed to free libssh2 scp subsystem: %d %s", rc, err_msg); } + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + sshc->ssh_channel = NULL; } if(sshc->sftp_session) { rc = libssh2_sftp_shutdown(sshc->sftp_session); + if((rc < 0) && data) { + char *err_msg = NULL; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "Failed to stop libssh2 sftp subsystem: %d %s", rc, err_msg); + } if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) return CURLE_AGAIN; - if((rc < 0) && data) - infof(data, "Failed to stop libssh2 sftp subsystem"); sshc->sftp_session = NULL; } if(sshc->ssh_session) { rc = libssh2_session_free(sshc->ssh_session); - if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) - return CURLE_AGAIN; - if((rc < 0) && data) { char *err_msg = NULL; (void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0); infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg); } + if(!block && (rc == LIBSSH2_ERROR_EAGAIN)) + return CURLE_AGAIN; + sshc->ssh_session = NULL; } @@ -3661,7 +3698,7 @@ static CURLcode scp_disconnect(struct Curl_easy *data, struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY); (void)dead_connection; - if(sshc && sshc->ssh_session && sshp) { + if(sshc && sshc->ssh_session) { /* only if there is a session still around to use! */ myssh_state(data, sshc, SSH_SESSION_DISCONNECT); result = ssh_block_statemach(data, sshc, sshp, TRUE); @@ -3834,7 +3871,7 @@ static CURLcode sftp_disconnect(struct Curl_easy *data, (void)dead_connection; if(sshc) { - if(sshc->ssh_session && sshp) { + if(sshc->ssh_session) { /* only if there is a session still around to use! */ DEBUGF(infof(data, "SSH DISCONNECT starts now")); myssh_state(data, sshc, SSH_SFTP_SHUTDOWN); -- 2.47.3