diff --git a/CVE-2026-59843.patch b/CVE-2026-59843.patch new file mode 100644 index 0000000..00db9ba --- /dev/null +++ b/CVE-2026-59843.patch @@ -0,0 +1,81 @@ +From 7e70cb3bbe43a7edd786995a5434812545827f27 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= +Date: Fri, 6 Mar 2026 13:58:30 +0100 +Subject: [PATCH 01/12] CVE-2026-59843 channels: Fail when receiving max packet + size 0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Do this both for SSH2_MSG_CHANNEL_OPEN and for +SSH2_MSG_CHANNEL_OPEN_CONFIRMATION. Using the +max packet size 0 would lead to an infinite loop +in channel_write_common. + +Originally reported by Rinku Das on on 23th February. +Independently reported by Yi Lin on 26th February and +Haruto Kimura on 22nd March. + +We do not consider this as a security issue as connecting +to untrusted servers on the internet brings much worse +security consequences than hanging your clinet. + +Signed-off-by: Pavol Žáčik +Reviewed-by: Jakub Jelen +(cherry picked from commit 44b186fa17aff497dae420c59c003222e438103c) +--- + src/channels.c | 7 +++++++ + src/messages.c | 19 +++++++++++++++---- + 2 files changed, 22 insertions(+), 4 deletions(-) + +diff --git a/src/channels.c b/src/channels.c +index 9cc8ab2f..2b84352f 100644 +--- a/src/channels.c ++++ b/src/channels.c +@@ -192,6 +192,13 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ + if (rc != SSH_OK) + goto error; + ++ if (channel->remote_maxpacket == 0) { ++ SSH_LOG(SSH_LOG_RARE, ++ "Invalid maximum packet size 0 in " ++ "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); ++ goto error; ++ } ++ + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, +diff --git a/src/messages.c b/src/messages.c +index ceb42b2c..04e95690 100644 +--- a/src/messages.c ++++ b/src/messages.c +@@ -1160,10 +1160,21 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ + SSH_LOG(SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + +- ssh_buffer_unpack(packet,"ddd", +- &msg->channel_request_open.sender, +- &msg->channel_request_open.window, +- &msg->channel_request_open.packet_size); ++ rc = ssh_buffer_unpack(packet, ++ "ddd", ++ &msg->channel_request_open.sender, ++ &msg->channel_request_open.window, ++ &msg->channel_request_open.packet_size); ++ if (rc != SSH_OK){ ++ goto error; ++ } ++ ++ if (msg->channel_request_open.packet_size == 0) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); ++ goto error; ++ } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED){ + ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); +-- +2.54.0 + diff --git a/CVE-2026-59844.patch b/CVE-2026-59844.patch new file mode 100644 index 0000000..bb9222f --- /dev/null +++ b/CVE-2026-59844.patch @@ -0,0 +1,45 @@ +From 7baf12bd38832bf7425de45e8f32d48af3e7fb7c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= +Date: Fri, 6 Mar 2026 18:05:29 +0100 +Subject: [PATCH 02/12] CVE-2026-59844 sftpserver: cap accepted values of len + in SSH_FXP_READ +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The client-provided length is directly used in +a malloc in process_read(), so not restricting it +leads to allocations bounded only by UINT32_MAX. + +The new cap is the same as the one currently used +by OpenSSH. + +Signed-off-by: Pavol Žáčik +Reviewed-by: Jakub Jelen +(cherry picked from commit 6dba2e06f0713c04ad5eca7d4315d0104be7e627) +--- + src/sftpserver.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/sftpserver.c b/src/sftpserver.c +index 528ef6f9..60f90714 100644 +--- a/src/sftpserver.c ++++ b/src/sftpserver.c +@@ -105,6 +105,14 @@ sftp_client_message sftp_get_client_message(sftp_session sftp) { + sftp_client_message_free(msg); + return NULL; + } ++ if (msg->len > MAX_PACKET_LEN - 1024) { ++ ssh_set_error(sftp->session, ++ SSH_FATAL, ++ "Too large SSH_FXP_READ length: %" PRIu32, ++ msg->len); ++ sftp_client_message_free(msg); ++ return NULL; ++ } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, +-- +2.54.0 + diff --git a/CVE-2026-59845.patch b/CVE-2026-59845.patch new file mode 100644 index 0000000..e46c32c --- /dev/null +++ b/CVE-2026-59845.patch @@ -0,0 +1,66 @@ +From 1ebfc368a59d4fe72cbca2a0813e1166fa8b95ca Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 26 Mar 2026 16:32:24 +0100 +Subject: [PATCH 03/12] CVE-2026-59845 socket: Properly check fork() return + code +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +During execution of proxy command, when fork() fails, its return value +is stored in pid and when the parent process attempts to kill it, +it sends the kill signal to all processes the calling application has +access to (except for init). + +This caused nard to debug issues when the system under the load was hitting +fork failures, which resulted in killing of all the system processes +(of given user). + +Reported and first patch iteration provided by: Halil Oktay (oblivionsage). + +This code missing fork return value check is in libssh since 2010 +(f31a14b7932ef4cc165ddd8f1f1a5b23eb21beb3), but this issue is exploitable only +since libssh 0.9.0 as previously there was no implementation of killing +ProxyCommand children. + +Signed-off-by: Jakub Jelen +Reviewed-by: Pavol Žáčik +(cherry picked from commit 92b6fb9c5e2d1606e8f809fd884ab6dd4d3b7d45) +--- + src/socket.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/src/socket.c b/src/socket.c +index 35838e86..6300fcb2 100644 +--- a/src/socket.c ++++ b/src/socket.c +@@ -942,6 +942,7 @@ ssh_execute_command(const char *command, socket_t in, socket_t out) + int + ssh_socket_connect_proxycommand(ssh_socket s, const char *command) + { ++ char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t pair[2]; + int pid; + int rc; +@@ -959,7 +960,17 @@ ssh_socket_connect_proxycommand(ssh_socket s, const char *command) + pid = fork(); + if (pid == 0) { + ssh_execute_command(command, pair[0], pair[0]); +- /* Does not return */ ++ /* child: Does not return */ ++ } ++ /* parent */ ++ if (pid == -1) { ++ close(pair[0]); ++ close(pair[1]); ++ ssh_set_error(s->session, ++ SSH_FATAL, ++ "fork failed: %s", ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ++ return SSH_ERROR; + } + s->proxy_pid = pid; + close(pair[0]); +-- +2.54.0 + diff --git a/CVE-2026-59846.patch b/CVE-2026-59846.patch new file mode 100644 index 0000000..1e0f472 --- /dev/null +++ b/CVE-2026-59846.patch @@ -0,0 +1,317 @@ +From d18c05327e8a6e9e8f7ddb26021a4fb7860e4afd Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Wed, 27 Dec 2023 20:32:18 +0100 +Subject: [PATCH 04/12] misc: Add function to check username syntax + +Malicious code can be injected using the username with metacharacters, +therefore the username must be validated before using it with any %u. + +Signed-off-by: Norbert Pocs +Reviewed-by: Jakub Jelen +--- + include/libssh/misc.h | 1 + + src/misc.c | 32 ++++++++++++++++++++++++++++++++ + tests/unittests/torture_misc.c | 34 ++++++++++++++++++++++++++++++++++ + 3 files changed, 67 insertions(+) + +diff --git a/include/libssh/misc.h b/include/libssh/misc.h +index 3452251d..06c62678 100644 +--- a/include/libssh/misc.h ++++ b/include/libssh/misc.h +@@ -103,6 +103,7 @@ int ssh_tmpname(char *template); + char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + + int ssh_check_hostname_syntax(const char *hostname); ++int ssh_check_username_syntax(const char *username); + + FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + +diff --git a/src/misc.c b/src/misc.c +index 0cca373a..0389ee70 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -2148,4 +2148,36 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) + return f; + } + ++/** ++ * @brief Checks syntax of a username ++ * ++ * This check disallows metacharacters in the username ++ * ++ * @param username The username to be checked, has to be null terminated ++ * ++ * @return SSH_OK if the username passes syntax check ++ * SSH_ERROR otherwise or if username is NULL or empty string ++ */ ++int ssh_check_username_syntax(const char *username) ++{ ++ size_t username_len; ++ ++ if (username == NULL || *username == '-') { ++ return SSH_ERROR; ++ } ++ ++ username_len = strlen(username); ++ if (username_len == 0 || username[username_len - 1] == '\\' || ++ strpbrk(username, "'`\";&<>|(){}") != NULL) { ++ return SSH_ERROR; ++ } ++ for (size_t i = 0; i < username_len; i++) { ++ if (isspace(username[i]) != 0 && username[i + 1] == '-') { ++ return SSH_ERROR; ++ } ++ } ++ ++ return SSH_OK; ++} ++ + /** @} */ +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index 4470c358..7c134304 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -839,6 +839,39 @@ static void torture_ssh_check_hostname_syntax(void **state) + assert_int_equal(rc, SSH_ERROR); + } + ++static void torture_ssh_check_username_syntax(void **state) { ++ int rc; ++ (void)state; ++ ++ rc = ssh_check_username_syntax("username"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("Alice"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("Alice and Bob"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("n4me?"); ++ assert_int_equal(rc, SSH_OK); ++ ++ rc = ssh_check_username_syntax("alice&bob"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("backslash\\"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("&var|()us\" +Date: Thu, 28 Dec 2023 12:16:29 +0100 +Subject: [PATCH 05/12] Check any input username for validity + +Check possible inputs of username for malicious code. + +Signed-off-by: Norbert Pocs +Reviewed-by: Jakub Jelen +--- + src/config_parser.c | 4 ++++ + src/misc.c | 10 ++++++++-- + src/options.c | 5 +++++ + 3 files changed, 17 insertions(+), 2 deletions(-) + +diff --git a/src/config_parser.c b/src/config_parser.c +index 5555f6fd..9163d374 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -203,6 +203,10 @@ int ssh_config_parse_uri(const char *tok, + if (*username == NULL) { + goto error; + } ++ rc = ssh_check_username_syntax(*username); ++ if (rc != SSH_OK) { ++ goto error; ++ } + } + tok = endp + 1; + /* If there is second @ character, this does not look like our URI */ +diff --git a/src/misc.c b/src/misc.c +index 0389ee70..b63b5521 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -183,6 +183,7 @@ char *ssh_get_local_username(void) + { + DWORD size = 0; + char *user; ++ int rc; + + /* get the size */ + GetUserName(NULL, &size); +@@ -193,7 +194,10 @@ char *ssh_get_local_username(void) + } + + if (GetUserName(user, &size)) { +- return user; ++ rc = ssh_check_username_syntax(user); ++ if (rc == SSH_OK) { ++ return user; ++ } + } + + return NULL; +@@ -337,8 +341,10 @@ char *ssh_get_local_username(void) + } + + name = strdup(pwd.pw_name); ++ rc = ssh_check_username_syntax(name); + +- if (name == NULL) { ++ if (rc != SSH_OK) { ++ free(name); + return NULL; + } + +diff --git a/src/options.c b/src/options.c +index a8023fbf..a40459ff 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -662,6 +662,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + ssh_set_error_oom(session); + return -1; + } ++ rc = ssh_check_username_syntax(session->opts.username); ++ if (rc != SSH_OK) { ++ ssh_set_error_invalid(session); ++ return -1; ++ } + } + break; + case SSH_OPTIONS_SSH_DIR: +-- +2.54.0 + +From 26ae62df976dfd381b72f201ed4281530cd63d86 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Thu, 28 Dec 2023 12:27:31 +0100 +Subject: [PATCH 06/12] torture: Add cases for username checks + +Signed-off-by: Norbert Pocs +Reviewed-by: Jakub Jelen +--- + tests/unittests/torture_config.c | 3 +++ + tests/unittests/torture_options.c | 6 ++++++ + 2 files changed, 9 insertions(+) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index 6dc59e48..09c769b9 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -2293,6 +2293,9 @@ static void torture_config_parse_uri(void **state) + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); ++ ++ rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); ++ assert_int_equal(rc, SSH_ERROR); + } + + /* Invalid configuration files +diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c +index 2a7a04de..5c3d3f2c 100644 +--- a/tests/unittests/torture_options.c ++++ b/tests/unittests/torture_options.c +@@ -89,6 +89,9 @@ static void torture_options_set_host(void **state) { + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "at@login"); + ++ /* disallow metacharacters in the username */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); ++ assert_ssh_return_code_equal(session, rc, SSH_ERROR); + } + + static void torture_options_set_ciphers(void **state) { +@@ -396,6 +399,9 @@ static void torture_options_set_user(void **state) { + assert_true(rc == 0); + #endif /* _WIN32 */ + ++ rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); ++ assert_ssh_return_code_equal(session, rc, SSH_ERROR); ++ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); + assert_true(rc == 0); + assert_string_equal(session->opts.username, "guru"); +-- +2.54.0 + +From 08a4f4c5151e75eda6d2105db493aa8c8df0dee7 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 2 Apr 2026 15:39:25 +0200 +Subject: [PATCH 07/12] CVE-2026-59846 Block shell metacharacters from + usernames +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When an attacker could sneak the dollar sign or backslash into the username +expanded for example in proxy command, it can result in printing environment +variables that might contain secrets. + +This is a fixup of CVE-2023-6004 which fixed this for hostnames, but these +two metacharacters were left out from the username filter. + +This keeps the list in one place to simplify maintenance. + +Signed-off-by: Jakub Jelen +Reviewed-by: Pavol Žáčik +(cherry picked from commit 6309df220e3431deb41946f892f4bb5af8b59dba) +--- + include/libssh/priv.h | 2 ++ + src/misc.c | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/include/libssh/priv.h b/include/libssh/priv.h +index 876ea4ed..22f1e6da 100644 +--- a/include/libssh/priv.h ++++ b/include/libssh/priv.h +@@ -223,6 +223,8 @@ int ssh_gettimeofday(struct timeval *__p, void *__t); + # define LIBSSH_MEM_PROTECTION + #endif + ++#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," ++ + /* forward declarations */ + struct ssh_common_struct; + struct ssh_kex_struct; +diff --git a/src/misc.c b/src/misc.c +index b63b5521..f4f48a37 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -2174,7 +2174,7 @@ int ssh_check_username_syntax(const char *username) + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || +- strpbrk(username, "'`\";&<>|(){}") != NULL) { ++ strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { +-- +2.54.0 + diff --git a/CVE-2026-59847.patch b/CVE-2026-59847.patch new file mode 100644 index 0000000..09191b2 --- /dev/null +++ b/CVE-2026-59847.patch @@ -0,0 +1,69 @@ +From ee32a1916a5d5545d4ac470646c0e023a152a406 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 May 2026 17:01:21 +0200 +Subject: [PATCH 08/12] CVE-2026-59847 libcrypto: Fix tag verification of + AES-GCM ciphers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +EVP_DecryptFinal() returns 0 errors, which was wrongly checked since +its introduction. + +Reported by Ben Smyth discuss@bensmyth.com + +Signed-off-by: Jakub Jelen +Reviewed-by: Pavol Žáčik +(cherry picked from commit 6d6cb6cb4d1684bb3f2b9fd23f3635e79d1e5495) +--- + src/libcrypto.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 658bbcd0..4353d006 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -680,7 +680,7 @@ evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, + rc = EVP_DecryptFinal(cipher->ctx, + NULL, + &outlen); +- if (rc < 0) { ++ if (rc != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication"); + return SSH_ERROR; + } +-- +2.54.0 + +From a7f55e18f9f7cb5ba3f71d8701168aea9d5a670f Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Mon, 18 May 2026 08:56:31 +0200 +Subject: [PATCH 09/12] CVE-2026-59847 libcrypto: Fix symmetric issue during + encryption +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jakub Jelen +Reviewed-by: Pavol Žáčik +(cherry picked from commit a5173c6ad249f7960bc7c1cc75a6a05ead8e3eba) +--- + src/libcrypto.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 4353d006..2f11caca 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -592,7 +592,7 @@ evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, + rc = EVP_EncryptFinal(cipher->ctx, + NULL, + &tmplen); +- if (rc < 0) { ++ if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); + return; + } +-- +2.54.0 + diff --git a/CVE-2026-59848.patch b/CVE-2026-59848.patch new file mode 100644 index 0000000..4856e95 --- /dev/null +++ b/CVE-2026-59848.patch @@ -0,0 +1,866 @@ +From 215615da6a174beaeafeccd69ae952d4106c6412 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= +Date: Mon, 1 Jun 2026 16:33:03 +0200 +Subject: [PATCH 10/12] CVE-2026-59848 sftp: handle responses with unknown + request IDs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds a new field to sftp_session_struct, +containing a list of outstanding request IDs. +An ID is added to the list when a request +is constructed and removed when the corresponding +request is received. If a client receives a response +with an unknown request ID, it reports an error. + +Storing responses with unknown request IDs in +the response queue could be abused by a malicious +SFTP server which could deplete client memory +this way. + +Signed-off-by: Pavol Žáčik +Reviewed-by: Jakub Jelen +(cherry picked from commit 26147eb4767937c797f97ff3b1b1663384232417) +--- + include/libssh/sftp.h | 1 + + include/libssh/sftp_priv.h | 12 ++ + src/sftp.c | 216 ++++++++++++++++++++----- + tests/client/CMakeLists.txt | 1 + + tests/client/torture_sftp_request_id.c | 183 +++++++++++++++++++++ + 5 files changed, 370 insertions(+), 43 deletions(-) + create mode 100644 tests/client/torture_sftp_request_id.c + +diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h +index c855df8a..ef3a61a5 100644 +--- a/include/libssh/sftp.h ++++ b/include/libssh/sftp.h +@@ -90,6 +90,7 @@ struct sftp_session_struct { + void **handles; + sftp_ext ext; + sftp_packet read_packet; ++ struct ssh_list *outstanding_ids; + }; + + struct sftp_packet_struct { +diff --git a/include/libssh/sftp_priv.h b/include/libssh/sftp_priv.h +index ccde008a..56a1725c 100644 +--- a/include/libssh/sftp_priv.h ++++ b/include/libssh/sftp_priv.h +@@ -29,4 +29,16 @@ sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname); + ++/** ++ * @brief Assigns a new SFTP ID for new requests and assures there is no ++ * collision between them. ++ * ++ * @param sftp The sftp session handle. ++ * @param id_out Pointer to store the new ID. ++ * ++ * @returns SSH_OK on success with the new ID stored in *id ++ * @returns SSH_ERROR on failure with the sftp and ssh errors set ++ */ ++int sftp_get_new_id(sftp_session sftp, uint32_t *id_out); ++ + #endif /* SFTP_PRIV_H */ +diff --git a/src/sftp.c b/src/sftp.c +index 70f9ed15..39f0639c 100644 +--- a/src/sftp.c ++++ b/src/sftp.c +@@ -149,6 +149,12 @@ sftp_session sftp_new(ssh_session session) + goto error; + } + ++ sftp->outstanding_ids = ssh_list_new(); ++ if (sftp->outstanding_ids == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ + if (ssh_channel_open_session(sftp->channel)) { + goto error; + } +@@ -165,6 +171,7 @@ error: + if (sftp->channel != NULL) { + ssh_channel_free(sftp->channel); + } ++ ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); +@@ -196,6 +203,12 @@ sftp_new_channel(ssh_session session, ssh_channel channel) + goto error; + } + ++ sftp->outstanding_ids = ssh_list_new(); ++ if (sftp->outstanding_ids == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); +@@ -217,6 +230,7 @@ error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } ++ ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); +@@ -358,6 +372,7 @@ void sftp_server_free(sftp_session sftp) + void sftp_free(sftp_session sftp) + { + sftp_request_queue ptr; ++ struct ssh_iterator *id_it = NULL; + + if (sftp == NULL) { + return; +@@ -384,6 +399,12 @@ void sftp_free(sftp_session sftp) + + sftp_ext_free(sftp->ext); + ++ id_it = ssh_list_get_iterator(sftp->outstanding_ids); ++ for (; id_it != NULL; id_it = id_it->next) { ++ free((uint32_t *)id_it->data); ++ } ++ ssh_list_free(sftp->outstanding_ids); ++ + SAFE_FREE(sftp); + } + +@@ -571,6 +592,8 @@ static sftp_message sftp_get_message(sftp_packet packet) + { + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; ++ struct ssh_iterator *id_it = NULL; ++ bool id_found = false; + int rc; + + switch(packet->type) { +@@ -618,6 +641,28 @@ static sftp_message sftp_get_message(sftp_packet packet) + msg->id, + msg->packet_type); + ++ /* Validate that this ID is in our outstanding requests list */ ++ id_it = ssh_list_get_iterator(sftp->outstanding_ids); ++ for (; id_it != NULL; id_it = id_it->next) { ++ uint32_t *stored_id = (uint32_t *)id_it->data; ++ if (*stored_id == msg->id) { ++ id_found = true; ++ ssh_list_remove(sftp->outstanding_ids, id_it); ++ free(stored_id); ++ break; ++ } ++ } ++ ++ if (!id_found) { ++ ssh_set_error(packet->sftp->session, ++ SSH_FATAL, ++ "Unknown request ID %" PRIu32, ++ msg->id); ++ sftp_message_free(msg); ++ sftp_set_error(packet->sftp, SSH_FX_FAILURE); ++ return NULL; ++ } ++ + return msg; + } + +@@ -902,13 +947,36 @@ static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ + return NULL; + } + +-/* +- * Assigns a new SFTP ID for new requests and assures there is no collision +- * between them. +- * Returns a new ID ready to use in a request +- */ +-static inline uint32_t sftp_get_new_id(sftp_session session) { +- return ++session->id_counter; ++int sftp_get_new_id(sftp_session sftp, uint32_t *id_out) ++{ ++ uint32_t *id = NULL; ++ int rc; ++ ++ if (id_out == NULL) { ++ ssh_set_error_invalid(sftp->session); ++ sftp_set_error(sftp, SSH_FX_FAILURE); ++ return SSH_ERROR; ++ } ++ ++ id = malloc(sizeof(uint32_t)); ++ if (id == NULL) { ++ ssh_set_error_oom(sftp->session); ++ sftp_set_error(sftp, SSH_FX_FAILURE); ++ return SSH_ERROR; ++ } ++ ++ *id = ++sftp->id_counter; ++ rc = ssh_list_append(sftp->outstanding_ids, id); ++ if (rc != SSH_OK) { ++ free(id); ++ ssh_set_error_oom(sftp->session); ++ sftp_set_error(sftp, SSH_FX_FAILURE); ++ return SSH_ERROR; ++ } ++ ++ *id_out = *id; ++ ++ return SSH_OK; + } + + static sftp_status_message parse_status_msg(sftp_message msg){ +@@ -1022,6 +1090,11 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) + return NULL; + } + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); +@@ -1029,8 +1102,6 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(payload, + "ds", + id, +@@ -1564,6 +1635,11 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) + int rc; + + if (dir->buffer == NULL) { ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); +@@ -1571,8 +1647,6 @@ sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(payload, + "dS", + id, +@@ -1697,6 +1771,11 @@ static int sftp_handle_close(sftp_session sftp, ssh_string handle) + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -1704,8 +1783,6 @@ static int sftp_handle_close(sftp_session sftp, ssh_string handle) + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "dS", + id, +@@ -1812,6 +1889,11 @@ sftp_file sftp_open(sftp_session sftp, + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -1839,7 +1921,6 @@ sftp_file sftp_open(sftp_session sftp, + sftp_flags |= SSH_FXF_APPEND; + } + SSH_LOG(SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); +- id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dsd", +@@ -1955,7 +2036,10 @@ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { + return -1; + } + +- id = sftp_get_new_id(handle->sftp); ++ rc = sftp_get_new_id(handle->sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqd", +@@ -2056,7 +2140,10 @@ int sftp_async_read_begin(sftp_file file, uint32_t len){ + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqd", +@@ -2187,7 +2274,10 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { + return -1; + } + +- id = sftp_get_new_id(file->sftp); ++ rc = sftp_get_new_id(file->sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqdP", +@@ -2298,6 +2388,11 @@ int sftp_unlink(sftp_session sftp, const char *file) { + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2305,8 +2400,6 @@ int sftp_unlink(sftp_session sftp, const char *file) { + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -2373,6 +2466,11 @@ int sftp_rmdir(sftp_session sftp, const char *directory) { + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2380,8 +2478,6 @@ int sftp_rmdir(sftp_session sftp, const char *directory) { + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -2446,6 +2542,11 @@ int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2457,8 +2558,6 @@ int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -2545,6 +2644,11 @@ int sftp_rename(sftp_session sftp, const char *original, const char *newname) { + uint32_t id; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2552,8 +2656,6 @@ int sftp_rename(sftp_session sftp, const char *original, const char *newname) { + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "dss", + id, +@@ -2629,6 +2731,11 @@ int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) + sftp_status_message status = NULL; + int rc; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2636,8 +2743,6 @@ int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -2759,6 +2864,11 @@ int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { + return -1; + } + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2766,8 +2876,6 @@ int sftp_symlink(sftp_session sftp, const char *target, const char *dest) { + return -1; + } + +- id = sftp_get_new_id(sftp); +- + /* TODO check for version number if they ever fix it. */ + if (ssh_get_openssh_version(sftp->session)) { + rc = ssh_buffer_pack(buffer, +@@ -2857,6 +2965,12 @@ char *sftp_readlink(sftp_session sftp, const char *path) + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } ++ ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2864,8 +2978,6 @@ char *sftp_readlink(sftp_session sftp, const char *path) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -2983,6 +3095,11 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) + return NULL; + } + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -2990,8 +3107,6 @@ sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "dss", + id, +@@ -3058,6 +3173,11 @@ int sftp_fsync(sftp_file file) + } + sftp = file->sftp; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return -1; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -3065,8 +3185,6 @@ int sftp_fsync(sftp_file file) + return -1; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "dsS", + id, +@@ -3158,6 +3276,11 @@ sftp_statvfs_t sftp_fstatvfs(sftp_file file) + } + sftp = file->sftp; + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -3165,8 +3288,6 @@ sftp_statvfs_t sftp_fstatvfs(sftp_file file) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "dsS", + id, +@@ -3245,6 +3366,11 @@ char *sftp_canonicalize_path(sftp_session sftp, const char *path) + return NULL; + } + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -3252,8 +3378,6 @@ char *sftp_canonicalize_path(sftp_session sftp, const char *path) + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -3336,6 +3460,11 @@ static sftp_attributes sftp_xstat(sftp_session sftp, + return NULL; + } + ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); +@@ -3343,8 +3472,6 @@ static sftp_attributes sftp_xstat(sftp_session sftp, + return NULL; + } + +- id = sftp_get_new_id(sftp); +- + rc = ssh_buffer_pack(buffer, + "ds", + id, +@@ -3414,6 +3541,11 @@ sftp_attributes sftp_fstat(sftp_file file) + return NULL; + } + ++ rc = sftp_get_new_id(file->sftp, &id); ++ if (rc != SSH_OK) { ++ return NULL; ++ } ++ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); +@@ -3421,8 +3553,6 @@ sftp_attributes sftp_fstat(sftp_file file) + return NULL; + } + +- id = sftp_get_new_id(file->sftp); +- + rc = ssh_buffer_pack(buffer, + "dS", + id, +diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt +index 71e5182e..864478a7 100644 +--- a/tests/client/CMakeLists.txt ++++ b/tests/client/CMakeLists.txt +@@ -49,6 +49,7 @@ if (WITH_SFTP) + torture_sftp_dir + torture_sftp_read + torture_sftp_fsync ++ torture_sftp_request_id + ${SFTP_BENCHMARK_TESTS}) + endif (WITH_SFTP) + +diff --git a/tests/client/torture_sftp_request_id.c b/tests/client/torture_sftp_request_id.c +new file mode 100644 +index 00000000..21f2774a +--- /dev/null ++++ b/tests/client/torture_sftp_request_id.c +@@ -0,0 +1,183 @@ ++#include "config.h" ++ ++#define LIBSSH_STATIC ++ ++#include "sftp.c" ++#include "torture.h" ++ ++#include ++#include ++ ++static int sshd_setup(void **state) ++{ ++ torture_setup_sshd_server(state, false); ++ ++ return 0; ++} ++ ++static int sshd_teardown(void **state) ++{ ++ torture_teardown_sshd_server(state); ++ ++ return 0; ++} ++ ++static int session_setup(void **state) ++{ ++ struct torture_state *s = *state; ++ struct passwd *pwd = NULL; ++ int rc; ++ ++ pwd = getpwnam("bob"); ++ assert_non_null(pwd); ++ ++ rc = setuid(pwd->pw_uid); ++ assert_return_code(rc, errno); ++ ++ s->ssh.session = torture_ssh_session(s, ++ TORTURE_SSH_SERVER, ++ NULL, ++ TORTURE_SSH_USER_ALICE, ++ NULL); ++ assert_non_null(s->ssh.session); ++ ++ s->ssh.tsftp = torture_sftp_session(s->ssh.session); ++ assert_non_null(s->ssh.tsftp); ++ ++ return 0; ++} ++ ++static int session_teardown(void **state) ++{ ++ struct torture_state *s = *state; ++ ++ torture_rmdirs(s->ssh.tsftp->testdir); ++ torture_sftp_close(s->ssh.tsftp); ++ ssh_disconnect(s->ssh.session); ++ ssh_free(s->ssh.session); ++ ++ return 0; ++} ++ ++static void torture_sftp_request_id_null(void **state) ++{ ++ struct torture_state *s = *state; ++ struct torture_sftp *t = s->ssh.tsftp; ++ sftp_session sftp = t->sftp; ++ int rc; ++ ++ rc = sftp_get_new_id(sftp, NULL); ++ assert_int_equal(rc, SSH_ERROR); ++} ++ ++static void torture_sftp_request_id_add(void **state) ++{ ++ struct torture_state *s = *state; ++ struct torture_sftp *t = s->ssh.tsftp; ++ sftp_session sftp = t->sftp; ++ uint32_t id1, id2; ++ int rc; ++ size_t count; ++ ++ /* The list of IDs should be empty at first */ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 0); ++ ++ /* Request a new ID */ ++ rc = sftp_get_new_id(sftp, &id1); ++ assert_int_equal(rc, SSH_OK); ++ ++ /* Check that the list has one ID now */ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 1); ++ ++ /* Request another ID */ ++ rc = sftp_get_new_id(sftp, &id2); ++ assert_int_equal(rc, SSH_OK); ++ ++ /* Check that the IDs differ */ ++ assert_int_not_equal(id1, id2); ++ ++ /* Check that the list has two IDs now */ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 2); ++} ++ ++static void torture_sftp_request_id_remove(void **state) ++{ ++ struct torture_state *s = *state; ++ struct torture_sftp *t = s->ssh.tsftp; ++ sftp_session sftp = t->sftp; ++ sftp_attributes attr = NULL; ++ size_t count; ++ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 0); ++ ++ /* We send a request and receive a response */ ++ attr = sftp_stat(sftp, SSH_EXECUTABLE); ++ assert_non_null(attr); ++ ++ /* The number of outstanding requests should be back to 0 */ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 0); ++ ++ sftp_attributes_free(attr); ++} ++ ++static void torture_sftp_request_id_unknown(void **state) ++{ ++ struct torture_state *s = *state; ++ struct torture_sftp *t = s->ssh.tsftp; ++ sftp_session sftp = t->sftp; ++ ssh_buffer buffer = NULL; ++ sftp_message msg = NULL; ++ uint32_t id = 0; ++ int rc; ++ size_t count; ++ ++ count = ssh_list_count(sftp->outstanding_ids); ++ assert_int_equal(count, 0); ++ ++ buffer = ssh_buffer_new(); ++ assert_non_null(buffer); ++ ++ rc = ssh_buffer_pack(buffer, "ds", id, "/tmp"); ++ assert_int_equal(rc, SSH_OK); ++ ++ /* Send a request without saving the request ID */ ++ rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, buffer); ++ assert_int_not_equal(rc, -1); ++ SSH_BUFFER_FREE(buffer); ++ ++ /* An attempt to receive the response should fail */ ++ rc = sftp_recv_response_msg(sftp, id, true, &msg); ++ assert_int_equal(rc, SSH_ERROR); ++} ++ ++int torture_run_tests(void) ++{ ++ int rc; ++ struct CMUnitTest tests[] = { ++ cmocka_unit_test_setup_teardown(torture_sftp_request_id_null, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_sftp_request_id_add, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_sftp_request_id_remove, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_sftp_request_id_unknown, ++ session_setup, ++ session_teardown), ++ }; ++ ++ ssh_init(); ++ ++ torture_filter_tests(tests); ++ rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); ++ ssh_finalize(); ++ ++ return rc; ++} +-- +2.54.0 + +From 2887a8e4cab51f9c04f1fcd956cd5cb9ba27049f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= +Date: Wed, 3 Jun 2026 12:56:09 +0200 +Subject: [PATCH 11/12] CVE-2026-59848 sftp: Initialize sftp_request_queue ptr + in sftp_free +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Pavol Žáčik +Reviewed-by: Jakub Jelen +(cherry picked from commit 00876f7658fd265682708572122502188fa22076) +--- + src/sftp.c | 2 +- + tests/client/torture_sftp_request_id.c | 3 +-- + 2 files changed, 2 insertions(+), 3 deletions(-) + +diff --git a/src/sftp.c b/src/sftp.c +index 39f0639c..91dc4474 100644 +--- a/src/sftp.c ++++ b/src/sftp.c +@@ -371,7 +371,7 @@ void sftp_server_free(sftp_session sftp) + + void sftp_free(sftp_session sftp) + { +- sftp_request_queue ptr; ++ sftp_request_queue ptr = NULL; + struct ssh_iterator *id_it = NULL; + + if (sftp == NULL) { +diff --git a/tests/client/torture_sftp_request_id.c b/tests/client/torture_sftp_request_id.c +index 21f2774a..30bff573 100644 +--- a/tests/client/torture_sftp_request_id.c ++++ b/tests/client/torture_sftp_request_id.c +@@ -131,7 +131,6 @@ static void torture_sftp_request_id_unknown(void **state) + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_buffer buffer = NULL; +- sftp_message msg = NULL; + uint32_t id = 0; + int rc; + size_t count; +@@ -151,7 +150,7 @@ static void torture_sftp_request_id_unknown(void **state) + SSH_BUFFER_FREE(buffer); + + /* An attempt to receive the response should fail */ +- rc = sftp_recv_response_msg(sftp, id, true, &msg); ++ rc = sftp_read_and_dispatch(sftp); + assert_int_equal(rc, SSH_ERROR); + } + +-- +2.54.0 + diff --git a/CVE-2026-59850.patch b/CVE-2026-59850.patch new file mode 100644 index 0000000..34fe24b --- /dev/null +++ b/CVE-2026-59850.patch @@ -0,0 +1,37 @@ +From 2b2dad066dbee0077e1214a8ec40386fc39f44f3 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Wed, 1 Jul 2026 16:43:08 +0200 +Subject: [PATCH 12/12] CVE-2026-59850 channels: Avoid processing DATA packets + on closed channels +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jakub Jelen +Reviewed-by: Pavol Žáčik +(cherry picked from commit a8a3fa352bb5213e08a35e4494c6e44360e2e38a) +--- + src/channels.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/channels.c b/src/channels.c +index 2b84352f..1c86fde8 100644 +--- a/src/channels.c ++++ b/src/channels.c +@@ -590,6 +590,13 @@ SSH_PACKET_CALLBACK(channel_rcv_data){ + return SSH_PACKET_USED; + } + ++ if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { ++ SSH_LOG(SSH_LOG_WARNING, "Received data on (remotely) closed channel"); ++ ssh_set_error(session, SSH_FATAL, "Received data on (remotely) closed channel"); ++ SSH_STRING_FREE(str); ++ return SSH_PACKET_USED; ++ } ++ + if (len <= channel->local_window) { + channel->local_window -= len; + } else { +-- +2.54.0 + diff --git a/libssh.spec b/libssh.spec index 31d8bf8..f7ceee8 100644 --- a/libssh.spec +++ b/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.10.4 -Release: 18%{?dist} +Release: 19%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -68,6 +68,13 @@ Patch26: CVE-2026-0965.patch Patch27: CVE-2026-0966.patch Patch28: CVE-2026-0967.patch Patch29: CVE-2026-0968.patch +Patch30: CVE-2026-59843.patch +Patch31: CVE-2026-59844.patch +Patch32: CVE-2026-59845.patch +Patch33: CVE-2026-59846.patch +Patch34: CVE-2026-59847.patch +Patch35: CVE-2026-59848.patch +Patch36: CVE-2026-59850.patch %description The ssh library was designed to be used by programmers needing a working SSH @@ -160,6 +167,16 @@ popd %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config %changelog +* Thu Jul 30 2026 Pavol Žáčik - 0.10.4-19 +- Backport CVE patches from 0.11.5 + Resolves: RHEL-213033 + Resolves: RHEL-213041 + Resolves: RHEL-213047 + Resolves: RHEL-213081 + Resolves: RHEL-213114 + Resolves: RHEL-213119 + Resolves: RHEL-213151 + * Fri Feb 13 2026 Pavol Žáčik - 0.10.4-18 - Resolves: RHEL-150661 - Resolves: CVE-2025-4877