From 7ae10cb5a6039746d01f21af166e2d4427d63f56 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Wed, 27 Mar 2024 19:59:42 +0000 Subject: [PATCH] import CS libssh-0.9.6-14.el8 --- SOURCES/CVE-2023-48795.patch | 723 ++++++++++ SOURCES/CVE-2023-6004.patch | 1114 ++++++++++++++++ SOURCES/CVE-2023-6918.patch | 1577 ++++++++++++++++++++++ SOURCES/auth_bypass.patch | 86 ++ SOURCES/covscan23.patch | 228 ++++ SOURCES/fix_tests.patch | 668 ++++++++++ SOURCES/null_dereference_rekey.patch | 1845 ++++++++++++++++++++++++++ SPECS/libssh.spec | 46 +- 8 files changed, 6286 insertions(+), 1 deletion(-) create mode 100644 SOURCES/CVE-2023-48795.patch create mode 100644 SOURCES/CVE-2023-6004.patch create mode 100644 SOURCES/CVE-2023-6918.patch create mode 100644 SOURCES/auth_bypass.patch create mode 100644 SOURCES/covscan23.patch create mode 100644 SOURCES/fix_tests.patch create mode 100644 SOURCES/null_dereference_rekey.patch diff --git a/SOURCES/CVE-2023-48795.patch b/SOURCES/CVE-2023-48795.patch new file mode 100644 index 0000000..9ff6031 --- /dev/null +++ b/SOURCES/CVE-2023-48795.patch @@ -0,0 +1,723 @@ +From 87b93be5a2071be782aa84aa5a91544b18959d5e Mon Sep 17 00:00:00 2001 +From: Aris Adamantiadis +Date: Tue, 12 Dec 2023 23:09:57 +0100 +Subject: [PATCH 1/4] CVE-2023-48795: client side mitigation + +Signed-off-by: Aris Adamantiadis +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/packet.h | 1 + + include/libssh/session.h | 6 +++++ + src/curve25519.c | 19 +++---------- + src/dh-gex.c | 7 +---- + src/dh.c | 17 +++--------- + src/ecdh.c | 8 +----- + src/ecdh_crypto.c | 12 +++------ + src/ecdh_gcrypt.c | 10 +++---- + src/ecdh_mbedcrypto.c | 11 +++----- + src/kex.c | 34 +++++++++++++++++++---- + src/packet.c | 58 ++++++++++++++++++++++++++++++++++++++++ + src/packet_cb.c | 12 +++++++++ + 12 files changed, 126 insertions(+), 69 deletions(-) + +diff --git a/include/libssh/packet.h b/include/libssh/packet.h +index 561bba8e..c6fbc3fc 100644 +--- a/include/libssh/packet.h ++++ b/include/libssh/packet.h +@@ -63,6 +63,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info); + SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); + #endif + ++int ssh_packet_send_newkeys(ssh_session session); + int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); + int ssh_packet_parse_type(ssh_session session); + //int packet_flush(ssh_session session, int enforce_blocking); +diff --git a/include/libssh/session.h b/include/libssh/session.h +index 64e118ef..3cde0dd4 100644 +--- a/include/libssh/session.h ++++ b/include/libssh/session.h +@@ -80,6 +80,12 @@ enum ssh_pending_call_e { + * sending it twice during key exchange to simplify the state machine. */ + #define SSH_SESSION_FLAG_KEXINIT_SENT 4 + ++/* The current SSH2 session implements the "strict KEX" feature and should behave ++ * differently on SSH2_MSG_NEWKEYS. */ ++#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 ++/* Unexpected packets have been sent while the session was still unencrypted */ ++#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 ++ + /* codes to use with ssh_handle_packets*() */ + /* Infinite timeout */ + #define SSH_TIMEOUT_INFINITE -1 +diff --git a/src/curve25519.c b/src/curve25519.c +index 37654438..6b7b4238 100644 +--- a/src/curve25519.c ++++ b/src/curve25519.c +@@ -335,16 +335,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ + } + + /* Send the MSG_NEWKEYS */ +- if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { +- goto error; +- } +- +- rc=ssh_packet_send(session); ++ rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } +- +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +@@ -502,18 +496,13 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ + return SSH_ERROR; + } + +- /* Send the MSG_NEWKEYS */ +- rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); +- if (rc < 0) { +- goto error; +- } +- + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; +- rc = ssh_packet_send(session); ++ ++ /* Send the MSG_NEWKEYS */ ++ rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; + error: +diff --git a/src/dh-gex.c b/src/dh-gex.c +index 4a298542..f1880270 100644 +--- a/src/dh-gex.c ++++ b/src/dh-gex.c +@@ -287,15 +287,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) + } + + /* Send the MSG_NEWKEYS */ +- if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { +- goto error; +- } +- +- rc = ssh_packet_send(session); ++ rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +diff --git a/src/dh.c b/src/dh.c +index c265efcb..1d519c63 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -386,16 +386,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + } + + /* Send the MSG_NEWKEYS */ +- if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { +- goto error; +- } +- +- rc=ssh_packet_send(session); ++ rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } +- +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + error: +@@ -532,15 +526,12 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) + } + SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); + +- if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { +- ssh_buffer_reinit(session->out_buffer); +- goto error; +- } + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; +- if (ssh_packet_send(session) == SSH_ERROR) { ++ /* Send the MSG_NEWKEYS */ ++ rc = ssh_packet_send_newkeys(session); ++ if (rc == SSH_ERROR) { + goto error; + } +- SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); + + return SSH_OK; + error: +diff --git a/src/ecdh.c b/src/ecdh.c +index e5b11ba9..af80beec 100644 +--- a/src/ecdh.c ++++ b/src/ecdh.c +@@ -93,16 +93,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ + } + + /* Send the MSG_NEWKEYS */ +- if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { +- goto error; +- } +- +- rc=ssh_packet_send(session); ++ rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } +- +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c +index a1de27fd..62578c1b 100644 +--- a/src/ecdh_crypto.c ++++ b/src/ecdh_crypto.c +@@ -323,18 +323,12 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + goto error; + } + +- /* Send the MSG_NEWKEYS */ +- rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); +- if (rc < 0) { +- goto error; +- } +- + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; +- rc = ssh_packet_send(session); +- if (rc == SSH_ERROR){ ++ /* Send the MSG_NEWKEYS */ ++ rc = ssh_packet_send_newkeys(session); ++ if (rc == SSH_ERROR) { + goto error; + } +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; + error: +diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c +index d9c41bf9..dd4332d7 100644 +--- a/src/ecdh_gcrypt.c ++++ b/src/ecdh_gcrypt.c +@@ -372,17 +372,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + goto out; + } + +- ++ session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ +- rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); +- if (rc != SSH_OK) { ++ rc = ssh_packet_send_newkeys(session); ++ if (rc == SSH_ERROR) { + goto out; + } + +- session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; +- rc = ssh_packet_send(session); +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); +- + out: + gcry_sexp_release(param); + gcry_sexp_release(key); +diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c +index 718f1522..45251a42 100644 +--- a/src/ecdh_mbedcrypto.c ++++ b/src/ecdh_mbedcrypto.c +@@ -300,16 +300,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + goto out; + } + +- rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); +- if (rc < 0) { +- rc = SSH_ERROR; ++ session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; ++ /* Send the MSG_NEWKEYS */ ++ rc = ssh_packet_send_newkeys(session); ++ if (rc == SSH_ERROR) { + goto out; + } + +- session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; +- rc = ssh_packet_send(session); +- SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); +- + out: + mbedtls_ecp_group_free(&grp); + if (rc == SSH_ERROR) { +diff --git a/src/kex.c b/src/kex.c +index 3e5ca6ad..0772cae8 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -163,6 +163,9 @@ + + /* RFC 8308 */ + #define KEX_EXTENSION_CLIENT "ext-info-c" ++/* Strict kex mitigation against CVE-2023-48795 */ ++#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" ++#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" + + /* Allowed algorithms in FIPS mode */ + #define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ +@@ -491,6 +494,27 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + session->first_kex_follows_guess_wrong ? "wrong" : "right"); + } + ++ /* ++ * handle the "strict KEX" feature. If supported by peer, then set up the ++ * flag and verify packet sequence numbers. ++ */ ++ if (server_kex) { ++ ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], ++ KEX_STRICT_CLIENT); ++ if (ok) { ++ SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); ++ session->flags |= SSH_SESSION_FLAG_KEX_STRICT; ++ } ++ } else { ++ /* client kex */ ++ ok = ssh_match_group(crypto->server_kex.methods[SSH_KEX], ++ KEX_STRICT_SERVER); ++ if (ok) { ++ SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); ++ session->flags |= SSH_SESSION_FLAG_KEX_STRICT; ++ } ++ } ++ + if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports +@@ -767,21 +791,21 @@ int ssh_set_client_kex(ssh_session session) + return SSH_OK; + } + +- /* Here we append ext-info-c to the list of kex algorithms */ ++ /* Here we append ext-info-c and kex-strict-c-v00@openssh.com to the list of kex algorithms */ + kex = client->methods[SSH_KEX]; + len = strlen(kex); +- if (len + strlen(KEX_EXTENSION_CLIENT) + 2 < len) { ++ /* Comma, comma, nul byte */ ++ kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + strlen(KEX_STRICT_CLIENT ) + 1; ++ if (kex_len >= MAX_PACKET_LEN) { + /* Overflow */ + return SSH_ERROR; + } +- kex_len = len + strlen(KEX_EXTENSION_CLIENT) + 2; /* comma, NULL */ + kex_tmp = realloc(kex, kex_len); + if (kex_tmp == NULL) { +- free(kex); + ssh_set_error_oom(session); + return SSH_ERROR; + } +- snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_EXTENSION_CLIENT); ++ snprintf(kex_tmp + len, kex_len - len, ",%s,%s", KEX_EXTENSION_CLIENT, KEX_STRICT_CLIENT); + client->methods[SSH_KEX] = kex_tmp; + + return SSH_OK; +diff --git a/src/packet.c b/src/packet.c +index ca7a03b7..82965fb3 100644 +--- a/src/packet.c ++++ b/src/packet.c +@@ -1309,6 +1309,19 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) + } + #endif /* WITH_ZLIB */ + payloadsize = ssh_buffer_get_len(session->in_buffer); ++ if (session->recv_seq == UINT32_MAX) { ++ /* Overflowing sequence numbers is always fishy */ ++ if (crypto == NULL) { ++ /* don't allow sequence number overflow when unencrypted */ ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Incoming sequence number overflow"); ++ goto error; ++ } else { ++ SSH_LOG(SSH_LOG_WARNING, ++ "Incoming sequence number overflow"); ++ } ++ } + session->recv_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; +@@ -1331,7 +1344,19 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) + SSH_LOG(SSH_LOG_PACKET, + "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", + session->in_packet.type, packet_len, padding, compsize, payloadsize); ++ if (crypto == NULL) { ++ /* In strict kex, only a few packets are allowed. Taint the session ++ * if we received packets that are normally allowed but to be ++ * refused if we are in strict kex when KEX is over. ++ */ ++ uint8_t type = session->in_packet.type; + ++ if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && ++ (type < SSH2_MSG_KEXDH_INIT || ++ type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { ++ session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; ++ } ++ } + /* Check if the packet is expected */ + filter_result = ssh_packet_incoming_filter(session); + +@@ -1347,6 +1372,9 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) + session->in_packet.type); + goto error; + case SSH_PACKET_UNKNOWN: ++ if (crypto == NULL) { ++ session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; ++ } + ssh_packet_send_unimplemented(session, session->recv_seq - 1); + break; + } +@@ -1521,7 +1549,33 @@ void ssh_packet_process(ssh_session session, uint8_t type) + SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", + ssh_get_error(session)); + } ++ if (session->current_crypto == NULL) { ++ session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; ++ } ++ } ++} ++ ++/** @internal ++ * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers ++ * @param session the SSH session ++ * @return SSH_ERROR on error, else SSH_OK ++ */ ++int ssh_packet_send_newkeys(ssh_session session) ++{ ++ int rc; ++ ++ /* Send the MSG_NEWKEYS */ ++ rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); ++ if (rc < 0) { ++ return rc; + } ++ ++ rc = ssh_packet_send(session); ++ if (rc == SSH_ERROR) { ++ return rc; ++ } ++ SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); ++ return rc; + } + + /** @internal +@@ -1829,6 +1883,10 @@ int ssh_packet_send(ssh_session session) + if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { + struct ssh_iterator *it; + ++ if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { ++ /* reset packet sequence number when running in strict kex mode */ ++ session->send_seq = 0; ++ } + for (it = ssh_list_get_iterator(session->out_queue); + it != NULL; + it = ssh_list_get_iterator(session->out_queue)) { +diff --git a/src/packet_cb.c b/src/packet_cb.c +index 3e4d5f6d..a08f1d8a 100644 +--- a/src/packet_cb.c ++++ b/src/packet_cb.c +@@ -110,6 +110,18 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){ + goto error; + } + ++ if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { ++ /* reset packet sequence number when running in strict kex mode */ ++ session->recv_seq = 0; ++ /* Check that we aren't tainted */ ++ if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Received unexpected packets in strict KEX mode."); ++ goto error; ++ } ++ } ++ + if(session->server){ + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; +-- +2.41.0 + + +From fd4948255560039b51c2d61f0a62784ed8b6f5a6 Mon Sep 17 00:00:00 2001 +From: Aris Adamantiadis +Date: Tue, 12 Dec 2023 23:30:26 +0100 +Subject: [PATCH 2/4] CVE-2023-48795: Server side mitigations + +Signed-off-by: Aris Adamantiadis +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/kex.h | 1 + + src/kex.c | 46 ++++++++++++++++++++++++++++++++++---------- + src/server.c | 8 +++++++- + 3 files changed, 44 insertions(+), 11 deletions(-) + +diff --git a/include/libssh/kex.h b/include/libssh/kex.h +index 2ace69b6..40da4ef2 100644 +--- a/include/libssh/kex.h ++++ b/include/libssh/kex.h +@@ -36,6 +36,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit); + int ssh_send_kex(ssh_session session); + void ssh_list_kex(struct ssh_kex_struct *kex); + int ssh_set_client_kex(ssh_session session); ++int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); + int ssh_kex_select_methods(ssh_session session); + int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); + char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); +diff --git a/src/kex.c b/src/kex.c +index 0772cae8..e37c176c 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -738,11 +738,8 @@ int ssh_set_client_kex(ssh_session session) + { + struct ssh_kex_struct *client = &session->next_crypto->client_kex; + const char *wanted; +- char *kex = NULL; +- char *kex_tmp = NULL; + int ok; + int i; +- size_t kex_len, len; + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ +@@ -791,11 +788,33 @@ int ssh_set_client_kex(ssh_session session) + return SSH_OK; + } + +- /* Here we append ext-info-c and kex-strict-c-v00@openssh.com to the list of kex algorithms */ +- kex = client->methods[SSH_KEX]; ++ ok = ssh_kex_append_extensions(session, client); ++ if (ok != SSH_OK){ ++ return ok; ++ } ++ ++ return SSH_OK; ++} ++ ++int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) ++{ ++ char *kex = NULL; ++ char *kex_tmp = NULL; ++ size_t kex_len, len; ++ ++ /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client ++ * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms ++ */ ++ kex = pkex->methods[SSH_KEX]; + len = strlen(kex); +- /* Comma, comma, nul byte */ +- kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + strlen(KEX_STRICT_CLIENT ) + 1; ++ if (session->server) { ++ /* Comma, nul byte */ ++ kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; ++ } else { ++ /* Comma, comma, nul byte */ ++ kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + ++ strlen(KEX_STRICT_CLIENT) + 1; ++ } + if (kex_len >= MAX_PACKET_LEN) { + /* Overflow */ + return SSH_ERROR; +@@ -805,9 +824,16 @@ int ssh_set_client_kex(ssh_session session) + ssh_set_error_oom(session); + return SSH_ERROR; + } +- snprintf(kex_tmp + len, kex_len - len, ",%s,%s", KEX_EXTENSION_CLIENT, KEX_STRICT_CLIENT); +- client->methods[SSH_KEX] = kex_tmp; +- ++ if (session->server){ ++ snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); ++ } else { ++ snprintf(kex_tmp + len, ++ kex_len - len, ++ ",%s,%s", ++ KEX_EXTENSION_CLIENT, ++ KEX_STRICT_CLIENT); ++ } ++ pkex->methods[SSH_KEX] = kex_tmp; + return SSH_OK; + } + +diff --git a/src/server.c b/src/server.c +index ed73e7fb..35e84465 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -195,7 +195,13 @@ int server_set_kex(ssh_session session) + } + } + +- return 0; ++ /* Do not append the extensions during rekey */ ++ if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { ++ return SSH_OK; ++ } ++ ++ rc = ssh_kex_append_extensions(session, server); ++ return rc; + } + + int ssh_server_init_kex(ssh_session session) { +-- +2.41.0 + + +From 03bbbc9e4c93aae2ccdd302d6123e4809be37746 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 14 Dec 2023 12:22:01 +0100 +Subject: [PATCH 3/4] CVE-2023-48795: Strip extensions from both kex lists for + matching + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + src/kex.c | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/src/kex.c b/src/kex.c +index e37c176c..eea3604b 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -936,11 +936,19 @@ int ssh_kex_select_methods (ssh_session session) + enum ssh_key_exchange_e kex_type; + int i; + +- /* Here we should drop the ext-info-c from the list so we avoid matching. ++ /* Here we should drop the extensions from the list so we avoid matching. + * it. We added it to the end, so we can just truncate the string here */ +- ext_start = strstr(client->methods[SSH_KEX], ","KEX_EXTENSION_CLIENT); +- if (ext_start != NULL) { +- ext_start[0] = '\0'; ++ if (session->client) { ++ ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); ++ if (ext_start != NULL) { ++ ext_start[0] = '\0'; ++ } ++ } ++ if (session->server) { ++ ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); ++ if (ext_start != NULL) { ++ ext_start[0] = '\0'; ++ } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { +-- +2.41.0 + + +From 768d1ed30cf4b3cb9628254ef3ee24b9c38abdbc Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 14 Dec 2023 12:47:48 +0100 +Subject: [PATCH 4/4] CVE-2023-48795: tests: Adjust calculation to strict kex + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + tests/client/torture_rekey.c | 56 ++++++++++++++++++++---------------- + 1 file changed, 32 insertions(+), 24 deletions(-) + +diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c +index 13c9a7fe..bfb273af 100644 +--- a/tests/client/torture_rekey.c ++++ b/tests/client/torture_rekey.c +@@ -148,6 +148,29 @@ static void torture_rekey_default(void **state) + ssh_disconnect(s->ssh.session); + } + ++static void sanity_check_session(void **state) ++{ ++ struct torture_state *s = *state; ++ struct ssh_crypto_struct *c = NULL; ++ ++ c = s->ssh.session->current_crypto; ++ assert_non_null(c); ++ assert_int_equal(c->in_cipher->max_blocks, ++ bytes / c->in_cipher->blocksize); ++ assert_int_equal(c->out_cipher->max_blocks, ++ bytes / c->out_cipher->blocksize); ++ /* when strict kex is used, the newkeys reset the sequence number */ ++ if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { ++ assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); ++ assert_int_equal(c->in_cipher->packets, s->ssh.session->recv_seq); ++ } else { ++ /* Otherwise we have less encrypted packets than transferred ++ * (first are not encrypted) */ ++ assert_true(c->out_cipher->packets < s->ssh.session->send_seq); ++ assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); ++ } ++} ++ + /* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +@@ -166,16 +189,10 @@ static void torture_rekey_send(void **state) + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + +- /* The blocks limit is set correctly */ +- c = s->ssh.session->current_crypto; +- assert_int_equal(c->in_cipher->max_blocks, +- bytes / c->in_cipher->blocksize); +- assert_int_equal(c->out_cipher->max_blocks, +- bytes / c->out_cipher->blocksize); +- /* We should have less encrypted packets than transfered (first are not encrypted) */ +- assert_true(c->out_cipher->packets < s->ssh.session->send_seq); +- assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); ++ sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ ++ c = s->ssh.session->current_crypto; ++ assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); +@@ -272,14 +289,10 @@ static void torture_rekey_recv(void **state) + sftp_file file; + mode_t mask; + +- /* The blocks limit is set correctly */ +- c = s->ssh.session->current_crypto; +- assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); +- assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); +- /* We should have less encrypted packets than transfered (first are not encrypted) */ +- assert_true(c->out_cipher->packets < s->ssh.session->send_seq); +- assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); ++ sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ ++ c = s->ssh.session->current_crypto; ++ assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); +@@ -464,15 +477,10 @@ static void torture_rekey_different_kex(void **state) + assert_ssh_return_code(s->ssh.session, rc); + + /* The blocks limit is set correctly */ +- c = s->ssh.session->current_crypto; +- assert_int_equal(c->in_cipher->max_blocks, +- bytes / c->in_cipher->blocksize); +- assert_int_equal(c->out_cipher->max_blocks, +- bytes / c->out_cipher->blocksize); +- /* We should have less encrypted packets than transfered (first are not encrypted) */ +- assert_true(c->out_cipher->packets < s->ssh.session->send_seq); +- assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); ++ sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ ++ c = s->ssh.session->current_crypto; ++ assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); +-- +2.41.0 + diff --git a/SOURCES/CVE-2023-6004.patch b/SOURCES/CVE-2023-6004.patch new file mode 100644 index 0000000..27a72d6 --- /dev/null +++ b/SOURCES/CVE-2023-6004.patch @@ -0,0 +1,1114 @@ +From 11bd6e6ad926a38cd7b9f8308a4c2fd8dfd9200c Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Sun, 5 Nov 2023 13:12:47 +0100 +Subject: [PATCH 01/12] CVE-2023-6004: torture_config: Allow multiple '@' in + usernames + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_config.c | 44 ++++++++++++++++++-------------- + 1 file changed, 25 insertions(+), 19 deletions(-) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index f91112a9..3a5a74bf 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -671,24 +671,40 @@ static void torture_config_proxyjump(void **state) { + assert_string_equal(session->opts.ProxyCommand, + "ssh -W [%h]:%p 2620:52:0::fed"); + +- /* Try to create some invalid configurations */ +- /* Non-numeric port */ ++ /* Multiple @ is allowed in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-port\n" +- "\tProxyJump jumpbox:22bad22\n" ++ "Host allowed-hostname\n" ++ "\tProxyJump localhost,user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); +- assert_ssh_return_code_equal(session, ret, SSH_ERROR); ++ assert_ssh_return_code(session, ret); ++ assert_string_equal(session->opts.ProxyCommand, ++ "ssh -J user@principal.com@jumpbox:22 -W [%h]:%p localhost"); + +- /* Too many @ */ ++ /* Multiple @ is allowed */ + torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-hostname\n" ++ "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); ++ ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); ++ assert_ssh_return_code(session, ret); ++ assert_string_equal(session->opts.ProxyCommand, ++ "ssh -l user@principal.com -p 22 -W [%h]:%p jumpbox"); ++ ++ /* In this part, we try various other config files and strings. */ ++ ++ /* Try to create some invalid configurations */ ++ /* Non-numeric port */ ++ torture_write_file(LIBSSH_TESTCONFIG11, ++ "Host bad-port\n" ++ "\tProxyJump jumpbox:22bad22\n" ++ ""); ++ torture_reset_config(session); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + +@@ -752,16 +768,6 @@ static void torture_config_proxyjump(void **state) { + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + +- /* Too many @ in second jump */ +- torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-hostname\n" +- "\tProxyJump localhost,user@principal.com@jumpbox:22\n" +- ""); +- torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); +- ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); +- assert_ssh_return_code_equal(session, ret, SSH_ERROR); +- + /* Braces mismatch in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host mismatch\n" +-- +2.41.0 + + +From c3234e5f94b96d6e29f0c1c82821c1e3ebb181ed Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Wed, 1 Nov 2023 11:24:43 +0100 +Subject: [PATCH 02/12] CVE-2023-6004: config_parser: Allow multiple '@' in + usernames + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/config_parser.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/config_parser.c b/src/config_parser.c +index ae2aa2c8..76cca224 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -152,7 +152,7 @@ int ssh_config_parse_uri(const char *tok, + } + + /* Username part (optional) */ +- endp = strchr(tok, '@'); ++ endp = strrchr(tok, '@'); + if (endp != NULL) { + /* Zero-length username is not valid */ + if (tok == endp) { +-- +2.41.0 + + +From a5b8bd0d8841296cf71d927824d60f576581243f Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 31 Oct 2023 09:48:52 +0100 +Subject: [PATCH 03/12] CVE-2023-6004: options: Simplify the hostname parsing + in ssh_options_set + +Using ssh_config_parse_uri can simplify the parsing of the host +parsing inside the function of ssh_options_set + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/options.c | 40 ++++++++++++++++------------------------ + 1 file changed, 16 insertions(+), 24 deletions(-) + +diff --git a/src/options.c b/src/options.c +index b5f951ac..7c03e7ab 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -36,6 +36,7 @@ + #include "libssh/session.h" + #include "libssh/misc.h" + #include "libssh/options.h" ++#include "libssh/config_parser.h" + #ifdef WITH_SERVER + #include "libssh/server.h" + #include "libssh/bind.h" +@@ -490,33 +491,24 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + ssh_set_error_invalid(session); + return -1; + } else { +- q = strdup(value); +- if (q == NULL) { +- ssh_set_error_oom(session); ++ char *username = NULL, *hostname = NULL, *port = NULL; ++ rc = ssh_config_parse_uri(value, &username, &hostname, &port); ++ if (rc != SSH_OK) { + return -1; + } +- p = strchr(q, '@'); +- +- SAFE_FREE(session->opts.host); +- +- if (p) { +- *p = '\0'; +- session->opts.host = strdup(p + 1); +- if (session->opts.host == NULL) { +- SAFE_FREE(q); +- ssh_set_error_oom(session); +- return -1; +- } +- ++ if (port != NULL) { ++ SAFE_FREE(username); ++ SAFE_FREE(hostname); ++ SAFE_FREE(port); ++ return -1; ++ } ++ if (username != NULL) { + SAFE_FREE(session->opts.username); +- session->opts.username = strdup(q); +- SAFE_FREE(q); +- if (session->opts.username == NULL) { +- ssh_set_error_oom(session); +- return -1; +- } +- } else { +- session->opts.host = q; ++ session->opts.username = username; ++ } ++ if (hostname != NULL) { ++ SAFE_FREE(session->opts.host); ++ session->opts.host = hostname; + } + } + break; +-- +2.41.0 + + +From efb24b6472e8b87c5832c0590f14e99e82fcdeeb Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 12:44:16 +0200 +Subject: [PATCH 04/12] CVE-2023-6004: misc: Add function to check allowed + characters of a hostname + +The hostname can be a domain name or an ip address. The colon has to be +allowed because of IPv6 even it is prohibited in domain names. + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + include/libssh/misc.h | 2 ++ + src/misc.c | 68 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 70 insertions(+) + +diff --git a/include/libssh/misc.h b/include/libssh/misc.h +index 3cc3b113..a5bee930 100644 +--- a/include/libssh/misc.h ++++ b/include/libssh/misc.h +@@ -97,4 +97,6 @@ int ssh_mkdirs(const char *pathname, mode_t mode); + int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); + int ssh_newline_vis(const char *string, char *buf, size_t buf_len); + ++int ssh_check_hostname_syntax(const char *hostname); ++ + #endif /* MISC_H_ */ +diff --git a/src/misc.c b/src/misc.c +index 149eb85e..e4239e81 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -94,6 +94,8 @@ + #define ZLIB_STRING "" + #endif + ++#define ARPA_DOMAIN_MAX_LEN 63 ++ + /** + * @defgroup libssh_misc The SSH helper functions. + * @ingroup libssh +@@ -1734,4 +1736,70 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) + return out - buf; + } + ++/** ++ * @brief Checks syntax of a domain name ++ * ++ * The check is made based on the RFC1035 section 2.3.1 ++ * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) ++ * ++ * The label should be no longer than 63 characters ++ * The label should start with a letter and end with a letter or number ++ * The label in this implementation can start with a number to allow virtual ++ * URLs to pass. Note that this will make IPv4 addresses to pass ++ * this check too. ++ * ++ * @param hostname The domain name to be checked, has to be null terminated ++ * ++ * @return SSH_OK if the hostname passes syntax check ++ * SSH_ERROR otherwise or if hostname is NULL or empty string ++ */ ++int ssh_check_hostname_syntax(const char *hostname) ++{ ++ char *it = NULL, *s = NULL, *buf = NULL; ++ size_t it_len; ++ char c; ++ ++ if (hostname == NULL || strlen(hostname) == 0) { ++ return SSH_ERROR; ++ } ++ ++ /* strtok_r writes into the string, keep the input clean */ ++ s = strdup(hostname); ++ if (s == NULL) { ++ return SSH_ERROR; ++ } ++ ++ it = strtok_r(s, ".", &buf); ++ /* if the token has 0 length */ ++ if (it == NULL) { ++ free(s); ++ return SSH_ERROR; ++ } ++ do { ++ it_len = strlen(it); ++ if (it_len > ARPA_DOMAIN_MAX_LEN || ++ /* the first char must be a letter, but some virtual urls start ++ * with a number */ ++ isalnum(it[0]) == 0 || ++ isalnum(it[it_len - 1]) == 0) { ++ free(s); ++ return SSH_ERROR; ++ } ++ while (*it != '\0') { ++ c = *it; ++ /* the "." is allowed too, but tokenization removes it from the ++ * string */ ++ if (isalnum(c) == 0 && c != '-') { ++ free(s); ++ return SSH_ERROR; ++ } ++ it++; ++ } ++ } while ((it = strtok_r(NULL, ".", &buf)) != NULL); ++ ++ free(s); ++ ++ return SSH_OK; ++} ++ + /** @} */ +-- +2.41.0 + + +From 234ecdf4d9705efa3727a54dcc1ddfe6377c7bf6 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 12:45:28 +0200 +Subject: [PATCH 05/12] CVE-2023-6004: torture_misc: Add test for + ssh_check_hostname_syntax + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 73 ++++++++++++++++++++++++++++++++++ + 1 file changed, 73 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index 0a48abbe..d14f4254 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -656,6 +656,78 @@ static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) + assert_string_equal(buffer, "a\\nb\\n"); + } + ++static void torture_ssh_check_hostname_syntax(void **state) ++{ ++ int rc; ++ (void)state; ++ ++ rc = ssh_check_hostname_syntax("duckduckgo.com"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("www.libssh.org"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("Some-Thing.com"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("localhost"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("a"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("a-0.b-b"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("libssh."); ++ assert_int_equal(rc, SSH_OK); ++ ++ rc = ssh_check_hostname_syntax(NULL); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(""); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("/"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("@"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("["); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("`"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("{"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("&"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("|"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("\""); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("`"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(" "); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("!www.libssh.org"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("--.--"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh-"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("."); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(".."); ++ assert_int_equal(rc, SSH_ERROR); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -678,6 +750,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_newline_vis), + cmocka_unit_test(torture_ssh_mkdirs), + cmocka_unit_test(torture_ssh_quote_file_name), ++ cmocka_unit_test(torture_ssh_check_hostname_syntax), + }; + + ssh_init(); +-- +2.41.0 + + +From 4d7ae19e9cd8c407012b40f3f2eaf480bfb1da7d Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 18:33:56 +0200 +Subject: [PATCH 06/12] CVE-2023-6004: config_parser: Check for valid syntax of + a hostname if it is a domain name + +This prevents code injection. +The domain name syntax checker is based on RFC1035. + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/config_parser.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/src/config_parser.c b/src/config_parser.c +index 76cca224..87bac5d4 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -30,6 +30,7 @@ + + #include "libssh/config_parser.h" + #include "libssh/priv.h" ++#include "libssh/misc.h" + + char *ssh_config_get_cmd(char **str) + { +@@ -139,6 +140,7 @@ int ssh_config_parse_uri(const char *tok, + { + char *endp = NULL; + long port_n; ++ int rc; + + /* Sanitize inputs */ + if (username != NULL) { +@@ -196,6 +198,14 @@ int ssh_config_parse_uri(const char *tok, + if (*hostname == NULL) { + goto error; + } ++ /* if not an ip, check syntax */ ++ rc = ssh_is_ipaddr(*hostname); ++ if (rc == 0) { ++ rc = ssh_check_hostname_syntax(*hostname); ++ if (rc != SSH_OK) { ++ goto error; ++ } ++ } + } + /* Skip also the closing bracket */ + if (*endp == ']') { +-- +2.41.0 + + +From 8cf4f4bfda968ab526c1a601ea1030bbaccaba17 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 10:28:47 +0200 +Subject: [PATCH 07/12] CVE-2023-6004: torture_proxycommand: Add test for + proxycommand injection + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/client/torture_proxycommand.c | 53 +++++++++++++++++++++++++++++ + 1 file changed, 53 insertions(+) + +diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c +index c04ff2ab..dc17f3d8 100644 +--- a/tests/client/torture_proxycommand.c ++++ b/tests/client/torture_proxycommand.c +@@ -161,6 +161,56 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) + assert_int_equal(rc & O_RDWR, O_RDWR); + } + ++static void torture_options_proxycommand_injection(void **state) ++{ ++ struct torture_state *s = *state; ++ struct passwd *pwd = NULL; ++ const char *malicious_host = "`echo foo > mfile`"; ++ const char *command = "nc %h %p"; ++ char *current_dir = NULL; ++ char *malicious_file_path = NULL; ++ int mfp_len; ++ int verbosity = torture_libssh_verbosity(); ++ struct stat sb; ++ int rc; ++ ++ pwd = getpwnam("bob"); ++ assert_non_null(pwd); ++ ++ rc = setuid(pwd->pw_uid); ++ assert_return_code(rc, errno); ++ ++ s->ssh.session = ssh_new(); ++ assert_non_null(s->ssh.session); ++ ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); ++ // if we would be checking the rc, this should fail ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); ++ ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); ++ assert_int_equal(rc, 0); ++ rc = ssh_connect(s->ssh.session); ++ assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); ++ ++ current_dir = torture_get_current_working_dir(); ++ assert_non_null(current_dir); ++ mfp_len = strlen(current_dir) + 6; ++ malicious_file_path = malloc(mfp_len); ++ assert_non_null(malicious_file_path); ++ rc = snprintf(malicious_file_path, mfp_len, ++ "%s/mfile", current_dir); ++ assert_int_equal(rc, mfp_len); ++ free(current_dir); ++ rc = stat(malicious_file_path, &sb); ++ assert_int_not_equal(rc, 0); ++ ++ // cleanup ++ remove(malicious_file_path); ++ free(malicious_file_path); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -176,6 +226,9 @@ int torture_run_tests(void) { + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, ++ NULL, ++ session_teardown), + }; + + +-- +2.41.0 + + +From a0dbe0d556e073804cc549802569577bb24757d9 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Mon, 6 Nov 2023 20:11:38 +0100 +Subject: [PATCH 08/12] CVE-2023-6004: torture_misc: Add test for ssh_is_ipaddr + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index d14f4254..073bc54c 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -728,6 +728,31 @@ static void torture_ssh_check_hostname_syntax(void **state) + assert_int_equal(rc, SSH_ERROR); + } + ++static void torture_ssh_is_ipaddr(void **state) { ++ int rc; ++ (void)state; ++ ++ rc = ssh_is_ipaddr("201.255.3.69"); ++ assert_int_equal(rc, 1); ++ rc = ssh_is_ipaddr("::1"); ++ assert_int_equal(rc, 1); ++ rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); ++ assert_int_equal(rc, 1); ++ ++ rc = ssh_is_ipaddr(".."); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr(":::"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("1.1.1.1.1"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("1.1"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("caesar"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("::xa:1"); ++ assert_int_equal(rc, 0); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -751,6 +776,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_mkdirs), + cmocka_unit_test(torture_ssh_quote_file_name), + cmocka_unit_test(torture_ssh_check_hostname_syntax), ++ cmocka_unit_test(torture_ssh_is_ipaddr), + }; + + ssh_init(); +-- +2.41.0 + + +From cdaec0d6273243a03f460cc5ba1a2265b4afb93a Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 28 Nov 2023 15:26:45 +0100 +Subject: [PATCH 09/12] CVE-2023-6004: misc: Add ipv6 link-local check for an + ip address + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/CMakeLists.txt | 17 ++++++++++------- + src/connect.c | 2 +- + src/misc.c | 44 ++++++++++++++++++++++++++++++++++++++------ + 3 files changed, 49 insertions(+), 14 deletions(-) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index a576cf71..fc401793 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -9,13 +9,6 @@ set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_REQUIRED_LIBRARIES} + ) + +-if (WIN32) +- set(LIBSSH_LINK_LIBRARIES +- ${LIBSSH_LINK_LIBRARIES} +- ws2_32 +- ) +-endif (WIN32) +- + if (OPENSSL_CRYPTO_LIBRARIES) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} +@@ -93,6 +86,16 @@ if (MINGW AND Threads_FOUND) + ) + endif() + ++# This needs to be last for mingw to build ++# https://gitlab.com/libssh/libssh-mirror/-/issues/84 ++if (WIN32) ++ set(LIBSSH_LINK_LIBRARIES ++ ${LIBSSH_LINK_LIBRARIES} ++ iphlpapi ++ ws2_32 ++ ) ++endif (WIN32) ++ + if (BUILD_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static +diff --git a/src/connect.c b/src/connect.c +index ce4d58df..ca62dcf0 100644 +--- a/src/connect.c ++++ b/src/connect.c +@@ -136,7 +136,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) + #endif + } + +- if (ssh_is_ipaddr(host)) { ++ if (ssh_is_ipaddr(host) == 1) { + /* this is an IP address */ + SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); + hints.ai_flags |= AI_NUMERICHOST; +diff --git a/src/misc.c b/src/misc.c +index e4239e81..6f5d2d60 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -32,6 +32,7 @@ + #include + #include + #include ++#include + + #endif /* _WIN32 */ + +@@ -59,6 +60,7 @@ + #include + #include + #include ++#include + + #ifdef HAVE_IO_H + #include +@@ -216,22 +218,37 @@ int ssh_is_ipaddr_v4(const char *str) { + + int ssh_is_ipaddr(const char *str) { + int rc = SOCKET_ERROR; ++ char *s = strdup(str); + +- if (strchr(str, ':')) { ++ if (s == NULL) { ++ return -1; ++ } ++ if (strchr(s, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); ++ char *network_interface = strchr(s, '%'); + +- /* TODO link-local (IP:v6:addr%ifname). */ +- rc = WSAStringToAddressA((LPSTR) str, ++ /* link-local (IP:v6:addr%ifname). */ ++ if (network_interface != NULL) { ++ rc = if_nametoindex(network_interface + 1); ++ if (rc == 0) { ++ free(s); ++ return 0; ++ } ++ *network_interface = '\0'; ++ } ++ rc = WSAStringToAddressA((LPSTR) s, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { ++ free(s); + return 1; + } + } + ++ free(s); + return ssh_is_ipaddr_v4(str); + } + #else /* _WIN32 */ +@@ -335,17 +352,32 @@ int ssh_is_ipaddr_v4(const char *str) { + + int ssh_is_ipaddr(const char *str) { + int rc = -1; ++ char *s = strdup(str); + +- if (strchr(str, ':')) { ++ if (s == NULL) { ++ return -1; ++ } ++ if (strchr(s, ':')) { + struct in6_addr dest6; ++ char *network_interface = strchr(s, '%'); + +- /* TODO link-local (IP:v6:addr%ifname). */ +- rc = inet_pton(AF_INET6, str, &dest6); ++ /* link-local (IP:v6:addr%ifname). */ ++ if (network_interface != NULL) { ++ rc = if_nametoindex(network_interface + 1); ++ if (rc == 0) { ++ free(s); ++ return 0; ++ } ++ *network_interface = '\0'; ++ } ++ rc = inet_pton(AF_INET6, s, &dest6); + if (rc > 0) { ++ free(s); + return 1; + } + } + ++ free(s); + return ssh_is_ipaddr_v4(str); + } + +-- +2.41.0 + + +From 6a8a18c73e73a338283dfbade0a7d83e5cfafe3b Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 28 Nov 2023 15:27:31 +0100 +Subject: [PATCH 10/12] CVE-2023-6004: torture_misc: Add tests for ipv6 + link-local + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index 073bc54c..f16b766e 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -17,7 +17,14 @@ + #include "misc.c" + #include "error.c" + ++#ifdef _WIN32 ++#include ++#else ++#include ++#endif ++ + #define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." ++#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" + + const char template[] = "temp_dir_XXXXXX"; + +@@ -730,14 +737,27 @@ static void torture_ssh_check_hostname_syntax(void **state) + + static void torture_ssh_is_ipaddr(void **state) { + int rc; ++ char *interf = malloc(64); ++ char *test_interf = malloc(128); + (void)state; + ++ assert_non_null(interf); ++ assert_non_null(test_interf); + rc = ssh_is_ipaddr("201.255.3.69"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("::1"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assert_int_equal(rc, 1); ++ if_indextoname(1, interf); ++ assert_non_null(interf); ++ rc = sprintf(test_interf, TORTURE_IPV6_LOCAL_LINK, interf); ++ /* the "%%s" is not written */ ++ assert_int_equal(rc, strlen(interf) + strlen(TORTURE_IPV6_LOCAL_LINK) - 3); ++ rc = ssh_is_ipaddr(test_interf); ++ assert_int_equal(rc, 1); ++ free(interf); ++ free(test_interf); + + rc = ssh_is_ipaddr(".."); + assert_int_equal(rc, 0); +-- +2.41.0 + + +From 72f59157e6ccbd4c0bb806690931413169a0886f Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 22 Dec 2023 10:32:40 +0100 +Subject: [PATCH 11/12] Fix regression in IPv6 addresses in hostname parsing + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +(cherry picked from commit 4f997aee7c7d7ea346b3e8ba505da0b7601ff318) +--- + include/libssh/config_parser.h | 11 ++++++++--- + src/config.c | 4 ++-- + src/config_parser.c | 16 +++++++++++----- + src/options.c | 10 ++-------- + 4 files changed, 23 insertions(+), 18 deletions(-) + +diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h +index e974917c..ee647bfb 100644 +--- a/include/libssh/config_parser.h ++++ b/include/libssh/config_parser.h +@@ -26,6 +26,8 @@ + #ifndef CONFIG_PARSER_H_ + #define CONFIG_PARSER_H_ + ++#include ++ + char *ssh_config_get_cmd(char **str); + + char *ssh_config_get_token(char **str); +@@ -45,13 +47,16 @@ int ssh_config_get_yesno(char **str, int notfound); + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. ++ * @param[in] ignore_port Set to true if the we should not attempt to parse ++ * port number. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ + int ssh_config_parse_uri(const char *tok, +- char **username, +- char **hostname, +- char **port); ++ char **username, ++ char **hostname, ++ char **port, ++ bool ignore_port); + + #endif /* LIBSSH_CONFIG_H_ */ +diff --git a/src/config.c b/src/config.c +index 54ada276..a813568d 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -324,7 +324,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) + } + if (parse_entry) { + /* We actually care only about the first item */ +- rv = ssh_config_parse_uri(cp, &username, &hostname, &port); ++ rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); + /* The rest of the list needs to be passed on */ + if (endp != NULL) { + next = strdup(endp + 1); +@@ -335,7 +335,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) + } + } else { + /* The rest is just sanity-checked to avoid failures later */ +- rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); ++ rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); + } + if (rv != SSH_OK) { + goto out; +diff --git a/src/config_parser.c b/src/config_parser.c +index 87bac5d4..a2da0a62 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -134,9 +134,10 @@ int ssh_config_get_yesno(char **str, int notfound) + } + + int ssh_config_parse_uri(const char *tok, +- char **username, +- char **hostname, +- char **port) ++ char **username, ++ char **hostname, ++ char **port, ++ bool ignore_port) + { + char *endp = NULL; + long port_n; +@@ -182,12 +183,17 @@ int ssh_config_parse_uri(const char *tok, + if (endp == NULL) { + goto error; + } +- } else { +- /* Hostnames or aliases expand to the last colon or to the end */ ++ } else if (!ignore_port) { ++ /* Hostnames or aliases expand to the last colon (if port is requested) ++ * or to the end */ + endp = strrchr(tok, ':'); + if (endp == NULL) { + endp = strchr(tok, '\0'); + } ++ } else { ++ /* If no port is requested, expand to the end of line ++ * (to accommodate the IPv6 addresses) */ ++ endp = strchr(tok, '\0'); + } + if (tok == endp) { + /* Zero-length hostnames are not valid */ +diff --git a/src/options.c b/src/options.c +index 7c03e7ab..0890ff2e 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -491,17 +491,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + ssh_set_error_invalid(session); + return -1; + } else { +- char *username = NULL, *hostname = NULL, *port = NULL; +- rc = ssh_config_parse_uri(value, &username, &hostname, &port); ++ char *username = NULL, *hostname = NULL; ++ rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { + return -1; + } +- if (port != NULL) { +- SAFE_FREE(username); +- SAFE_FREE(hostname); +- SAFE_FREE(port); +- return -1; +- } + if (username != NULL) { + SAFE_FREE(session->opts.username); + session->opts.username = username; +-- +2.41.0 + + +From 5dc10ff63ca2e8db91abfdccf1d095f5b4261b8e Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 22 Dec 2023 09:52:18 +0100 +Subject: [PATCH 12/12] tests: Increase test coverage for IPv6 address parsing + as hostnames + +This was an issue in cockpit: + +https://github.com/cockpit-project/cockpit/issues/19772 + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +(cherry picked from commit 6f6e453d7b0ad4ee6a6f6a1c96a9a6b27821410d) +--- + tests/unittests/torture_config.c | 49 +++++++++++++++++++++++++++++++ + tests/unittests/torture_options.c | 22 ++++++++++++++ + 2 files changed, 71 insertions(+) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index 3a5a74bf..d8097e79 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -2,6 +2,7 @@ + + #define LIBSSH_STATIC + ++#include + #include "torture.h" + #include "libssh/options.h" + #include "libssh/session.h" +@@ -997,6 +998,53 @@ static void torture_config_match_pattern(void **state) + + } + ++static void torture_config_parse_uri(void **state) ++{ ++ char *username = NULL; ++ char *hostname = NULL; ++ char *port = NULL; ++ int rc; ++ ++ (void)state; /* unused */ ++ ++ rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "localhost"); ++ SAFE_FREE(hostname); ++ assert_null(port); ++ ++ rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1.2.3.4"); ++ SAFE_FREE(hostname); ++ assert_null(port); ++ ++ rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1.2.3.4"); ++ SAFE_FREE(hostname); ++ assert_string_equal(port, "2222"); ++ SAFE_FREE(port); ++ ++ rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1:2:3::4"); ++ SAFE_FREE(hostname); ++ assert_string_equal(port, "2222"); ++ SAFE_FREE(port); ++ ++ /* do not want port */ ++ rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1:2:3::4"); ++ SAFE_FREE(hostname); ++} ++ + + int torture_run_tests(void) { + int rc; +@@ -1012,6 +1060,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_config_rekey), + cmocka_unit_test(torture_config_pubkeyacceptedkeytypes), + cmocka_unit_test(torture_config_match_pattern), ++ cmocka_unit_test(torture_config_parse_uri), + }; + + +diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c +index d0fdaed1..576ca9cd 100644 +--- a/tests/unittests/torture_options.c ++++ b/tests/unittests/torture_options.c +@@ -59,12 +59,34 @@ static void torture_options_set_host(void **state) { + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "localhost"); + ++ /* IPv4 address */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "127.1.1.1"); ++ assert_null(session->opts.username); ++ ++ /* IPv6 address */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "::1"); ++ assert_null(session->opts.username); ++ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "meditation"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "guru"); ++ ++ /* more @ in uri is OK -- it should go to the username */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "hostname"); ++ assert_non_null(session->opts.username); ++ assert_string_equal(session->opts.username, "at@login"); + } + + static void torture_options_set_ciphers(void **state) { +-- +2.41.0 + diff --git a/SOURCES/CVE-2023-6918.patch b/SOURCES/CVE-2023-6918.patch new file mode 100644 index 0000000..a5bd07d --- /dev/null +++ b/SOURCES/CVE-2023-6918.patch @@ -0,0 +1,1577 @@ +From 93c1dbd69f07f324c6aa1ab9296a632489cd3ead Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 10:30:09 +0100 +Subject: [PATCH 1/5] CVE-2023-6918: kdf: Reformat + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + src/kdf.c | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +diff --git a/src/kdf.c b/src/kdf.c +index 09644739..656a38ed 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -58,7 +58,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + } + + ctx->digest_type = type; +- switch(type){ ++ switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + return ctx; +@@ -79,7 +79,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + + static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) + { +- switch(ctx->digest_type){ ++ switch (ctx->digest_type) { + case SSH_KDF_SHA1: + sha1_update(ctx->ctx.sha1_ctx, data, len); + break; +@@ -97,26 +97,28 @@ static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) + + static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) + { +- switch(ctx->digest_type){ ++ switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_final(md,ctx->ctx.sha1_ctx); ++ sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_final(md,ctx->ctx.sha256_ctx); ++ sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_final(md,ctx->ctx.sha384_ctx); ++ sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_final(md,ctx->ctx.sha512_ctx); ++ sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); + } + + int sshkdf_derive_key(struct ssh_crypto_struct *crypto, +- unsigned char *key, size_t key_len, +- int key_type, unsigned char *output, ++ unsigned char *key, ++ size_t key_len, ++ int key_type, ++ unsigned char *output, + size_t requested_len) + { + /* Can't use VLAs with Visual Studio, so allocate the biggest +-- +2.41.0 + + +From 882d9cb5c8d37d93f9b349d517e59bf496817007 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 12:55:27 +0100 +Subject: [PATCH 2/5] CVE-2023-6918: Remove unused evp functions and types + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/libcrypto.h | 5 --- + include/libssh/libgcrypt.h | 1 - + include/libssh/libmbedcrypto.h | 1 - + include/libssh/wrapper.h | 5 --- + src/libcrypto.c | 55 +------------------------ + src/libgcrypt.c | 52 ------------------------ + src/libmbedcrypto.c | 74 ---------------------------------- + 7 files changed, 1 insertion(+), 192 deletions(-) + +diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h +index 4117942c..35b277c5 100644 +--- a/include/libssh/libcrypto.h ++++ b/include/libssh/libcrypto.h +@@ -39,11 +39,6 @@ typedef EVP_MD_CTX* SHA384CTX; + typedef EVP_MD_CTX* SHA512CTX; + typedef EVP_MD_CTX* MD5CTX; + typedef HMAC_CTX* HMACCTX; +-#ifdef HAVE_ECC +-typedef EVP_MD_CTX *EVPCTX; +-#else +-typedef void *EVPCTX; +-#endif + + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH + #define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h +index 347d851b..3a803fa4 100644 +--- a/include/libssh/libgcrypt.h ++++ b/include/libssh/libgcrypt.h +@@ -32,7 +32,6 @@ typedef gcry_md_hd_t SHA384CTX; + typedef gcry_md_hd_t SHA512CTX; + typedef gcry_md_hd_t MD5CTX; + typedef gcry_md_hd_t HMACCTX; +-typedef gcry_md_hd_t EVPCTX; + #define SHA_DIGEST_LENGTH 20 + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH + #define MD5_DIGEST_LEN 16 +diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h +index fe53019b..b6e3e2a3 100644 +--- a/include/libssh/libmbedcrypto.h ++++ b/include/libssh/libmbedcrypto.h +@@ -41,7 +41,6 @@ typedef mbedtls_md_context_t *SHA384CTX; + typedef mbedtls_md_context_t *SHA512CTX; + typedef mbedtls_md_context_t *MD5CTX; + typedef mbedtls_md_context_t *HMACCTX; +-typedef mbedtls_md_context_t *EVPCTX; + + #define SHA_DIGEST_LENGTH 20 + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h +index ba64939b..2f5ce189 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -90,11 +90,6 @@ void sha512_update(SHA512CTX c, const void *data, unsigned long len); + void sha512_final(unsigned char *md,SHA512CTX c); + void sha512(const unsigned char *digest, int len, unsigned char *hash); + +-void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen); +-EVPCTX evp_init(int nid); +-void evp_update(EVPCTX ctx, const void *data, unsigned long len); +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); +- + HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); + void hmac_update(HMACCTX c, const void *data, unsigned long len); + void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 3db75df6..5f3917ba 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -148,60 +148,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) + } + } + +-#ifdef HAVE_OPENSSL_ECC +-static const EVP_MD *nid_to_evpmd(int nid) +-{ +- switch (nid) { +- case NID_X9_62_prime256v1: +- return EVP_sha256(); +- case NID_secp384r1: +- return EVP_sha384(); +- case NID_secp521r1: +- return EVP_sha512(); +- default: +- return NULL; +- } +- +- return NULL; +-} +- +-void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) +-{ +- const EVP_MD *evp_md = nid_to_evpmd(nid); +- EVP_MD_CTX *md = EVP_MD_CTX_new(); +- +- EVP_DigestInit(md, evp_md); +- EVP_DigestUpdate(md, digest, len); +- EVP_DigestFinal(md, hash, hlen); +- EVP_MD_CTX_free(md); +-} +- +-EVPCTX evp_init(int nid) +-{ +- const EVP_MD *evp_md = nid_to_evpmd(nid); +- +- EVPCTX ctx = EVP_MD_CTX_new(); +- if (ctx == NULL) { +- return NULL; +- } +- +- EVP_DigestInit(ctx, evp_md); +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- EVP_DigestUpdate(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- EVP_DigestFinal(ctx, md, mdlen); +- EVP_MD_CTX_free(ctx); +-} +-#endif +- + SHA256CTX sha256_init(void) + { + int rc; +@@ -345,6 +291,7 @@ void md5_final(unsigned char *md, MD5CTX c) + EVP_MD_CTX_destroy(c); + } + ++ + #ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID + static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) + { +diff --git a/src/libgcrypt.c b/src/libgcrypt.c +index 8fbf2157..49488793 100644 +--- a/src/libgcrypt.c ++++ b/src/libgcrypt.c +@@ -82,58 +82,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + } + +-#ifdef HAVE_GCRYPT_ECC +-static int nid_to_md_algo(int nid) +-{ +- switch (nid) { +- case NID_gcrypt_nistp256: +- return GCRY_MD_SHA256; +- case NID_gcrypt_nistp384: +- return GCRY_MD_SHA384; +- case NID_gcrypt_nistp521: +- return GCRY_MD_SHA512; +- } +- return GCRY_MD_NONE; +-} +- +-void evp(int nid, unsigned char *digest, int len, +- unsigned char *hash, unsigned int *hlen) +-{ +- int algo = nid_to_md_algo(nid); +- +- /* Note: What gcrypt calls 'hash' is called 'digest' here and +- vice-versa. */ +- gcry_md_hash_buffer(algo, hash, digest, len); +- *hlen = gcry_md_get_algo_dlen(algo); +-} +- +-EVPCTX evp_init(int nid) +-{ +- gcry_error_t err; +- int algo = nid_to_md_algo(nid); +- EVPCTX ctx; +- +- err = gcry_md_open(&ctx, algo, 0); +- if (err) { +- return NULL; +- } +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- gcry_md_write(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- int algo = gcry_md_get_algo(ctx); +- *mdlen = gcry_md_get_algo_dlen(algo); +- memcpy(md, gcry_md_read(ctx, algo), *mdlen); +- gcry_md_close(ctx); +-} +-#endif + + SHA256CTX sha256_init(void) { + SHA256CTX ctx = NULL; +diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c +index a2e74d3b..f37a6a6d 100644 +--- a/src/libmbedcrypto.c ++++ b/src/libmbedcrypto.c +@@ -103,80 +103,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) + } + } + +-static mbedtls_md_type_t nid_to_md_algo(int nid) +-{ +- switch (nid) { +- case NID_mbedtls_nistp256: +- return MBEDTLS_MD_SHA256; +- case NID_mbedtls_nistp384: +- return MBEDTLS_MD_SHA384; +- case NID_mbedtls_nistp521: +- return MBEDTLS_MD_SHA512; +- } +- return MBEDTLS_MD_NONE; +-} +- +-void evp(int nid, unsigned char *digest, int len, +- unsigned char *hash, unsigned int *hlen) +-{ +- mbedtls_md_type_t algo = nid_to_md_algo(nid); +- const mbedtls_md_info_t *md_info = +- mbedtls_md_info_from_type(algo); +- +- +- if (md_info != NULL) { +- *hlen = mbedtls_md_get_size(md_info); +- mbedtls_md(md_info, digest, len, hash); +- } +-} +- +-EVPCTX evp_init(int nid) +-{ +- EVPCTX ctx = NULL; +- int rc; +- mbedtls_md_type_t algo = nid_to_md_algo(nid); +- const mbedtls_md_info_t *md_info = +- mbedtls_md_info_from_type(algo); +- +- if (md_info == NULL) { +- return NULL; +- } +- +- ctx = malloc(sizeof(mbedtls_md_context_t)); +- if (ctx == NULL) { +- return NULL; +- } +- +- mbedtls_md_init(ctx); +- +- rc = mbedtls_md_setup(ctx, md_info, 0); +- if (rc != 0) { +- SAFE_FREE(ctx); +- return NULL; +- } +- +- rc = mbedtls_md_starts(ctx); +- if (rc != 0) { +- SAFE_FREE(ctx); +- return NULL; +- } +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- mbedtls_md_update(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- *mdlen = mbedtls_md_get_size(ctx->md_info); +- mbedtls_md_finish(ctx, md); +- mbedtls_md_free(ctx); +- SAFE_FREE(ctx); +-} +- + SHA256CTX sha256_init(void) + { + SHA256CTX ctx = NULL; +-- +2.41.0 + + +From a45a3c940d17abb3bcd2b924ccd5cd68eb8fd753 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 12:55:54 +0100 +Subject: [PATCH 3/5] CVE-2023-6918: Systematically check return values when + calculating digests + +with all crypto backends + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/wrapper.h | 34 +++++--- + src/kdf.c | 96 +++++++++++++++++---- + src/libcrypto.c | 166 +++++++++++++++++++++++++++-------- + src/libgcrypt.c | 142 +++++++++++++++++++++++------- + src/libmbedcrypto.c | 182 ++++++++++++++++++++++++++++++--------- + src/session.c | 72 ++++++++++++---- + 6 files changed, 533 insertions(+), 159 deletions(-) + +diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h +index 2f5ce189..e6384b50 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -67,32 +67,38 @@ struct ssh_crypto_struct; + + typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; + MD5CTX md5_init(void); +-void md5_update(MD5CTX c, const void *data, unsigned long len); +-void md5_final(unsigned char *md,MD5CTX c); ++void md5_ctx_free(MD5CTX); ++int md5_update(MD5CTX c, const void *data, unsigned long len); ++int md5_final(unsigned char *md,MD5CTX c); + + SHACTX sha1_init(void); +-void sha1_update(SHACTX c, const void *data, unsigned long len); +-void sha1_final(unsigned char *md,SHACTX c); +-void sha1(const unsigned char *digest,int len,unsigned char *hash); ++void sha1_ctx_free(SHACTX); ++int sha1_update(SHACTX c, const void *data, unsigned long len); ++int sha1_final(unsigned char *md,SHACTX c); ++int sha1(const unsigned char *digest,int len,unsigned char *hash); + + SHA256CTX sha256_init(void); +-void sha256_update(SHA256CTX c, const void *data, unsigned long len); +-void sha256_final(unsigned char *md,SHA256CTX c); +-void sha256(const unsigned char *digest, int len, unsigned char *hash); ++void sha256_ctx_free(SHA256CTX); ++int sha256_update(SHA256CTX c, const void *data, unsigned long len); ++int sha256_final(unsigned char *md,SHA256CTX c); ++int sha256(const unsigned char *digest, int len, unsigned char *hash); + + SHA384CTX sha384_init(void); +-void sha384_update(SHA384CTX c, const void *data, unsigned long len); +-void sha384_final(unsigned char *md,SHA384CTX c); +-void sha384(const unsigned char *digest, int len, unsigned char *hash); ++void sha384_ctx_free(SHA384CTX); ++int sha384_update(SHA384CTX c, const void *data, unsigned long len); ++int sha384_final(unsigned char *md,SHA384CTX c); ++int sha384(const unsigned char *digest, int len, unsigned char *hash); + + SHA512CTX sha512_init(void); +-void sha512_update(SHA512CTX c, const void *data, unsigned long len); +-void sha512_final(unsigned char *md,SHA512CTX c); +-void sha512(const unsigned char *digest, int len, unsigned char *hash); ++void sha512_ctx_free(SHA512CTX); ++int sha512_update(SHA512CTX c, const void *data, unsigned long len); ++int sha512_final(unsigned char *md,SHA512CTX c); ++int sha512(const unsigned char *digest, int len, unsigned char *hash); + + HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); + void hmac_update(HMACCTX c, const void *data, unsigned long len); + void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); ++ + size_t hmac_digest_len(enum ssh_hmac_e type); + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/kdf.c b/src/kdf.c +index 656a38ed..90f6e9f3 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -77,41 +77,64 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + } + } + +-static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) ++static void ssh_mac_ctx_free(ssh_mac_ctx ctx) + { ++ if (ctx == NULL) { ++ return; ++ } ++ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_update(ctx->ctx.sha1_ctx, data, len); ++ sha1_ctx_free(ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_update(ctx->ctx.sha256_ctx, data, len); ++ sha256_ctx_free(ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_update(ctx->ctx.sha384_ctx, data, len); ++ sha384_ctx_free(ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_update(ctx->ctx.sha512_ctx, data, len); ++ sha512_ctx_free(ctx->ctx.sha512_ctx); + break; + } ++ SAFE_FREE(ctx); ++} ++ ++static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) ++{ ++ switch (ctx->digest_type) { ++ case SSH_KDF_SHA1: ++ return sha1_update(ctx->ctx.sha1_ctx, data, len); ++ case SSH_KDF_SHA256: ++ return sha256_update(ctx->ctx.sha256_ctx, data, len); ++ case SSH_KDF_SHA384: ++ return sha384_update(ctx->ctx.sha384_ctx, data, len); ++ case SSH_KDF_SHA512: ++ return sha512_update(ctx->ctx.sha512_ctx, data, len); ++ } ++ return SSH_ERROR; + } + +-static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) ++static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) + { ++ int rc = SSH_ERROR; ++ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_final(md, ctx->ctx.sha1_ctx); ++ rc = sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_final(md, ctx->ctx.sha256_ctx); ++ rc = sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_final(md, ctx->ctx.sha384_ctx); ++ rc = sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_final(md, ctx->ctx.sha512_ctx); ++ rc = sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); ++ return rc; + } + + int sshkdf_derive_key(struct ssh_crypto_struct *crypto, +@@ -127,6 +150,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + size_t output_len = crypto->digest_len; + char letter = key_type; + ssh_mac_ctx ctx; ++ int rc; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; +@@ -137,11 +161,30 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + return -1; + } + +- ssh_mac_update(ctx, key, key_len); +- ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); +- ssh_mac_update(ctx, &letter, 1); +- ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); +- ssh_mac_final(digest, ctx); ++ rc = ssh_mac_update(ctx, key, key_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, &letter, 1); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_final(digest, ctx); ++ if (rc != SSH_OK) { ++ return -1; ++ } + + if (requested_len < output_len) { + output_len = requested_len; +@@ -153,10 +196,25 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + if (ctx == NULL) { + return -1; + } +- ssh_mac_update(ctx, key, key_len); +- ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); +- ssh_mac_update(ctx, output, output_len); +- ssh_mac_final(digest, ctx); ++ rc = ssh_mac_update(ctx, key, key_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, output, output_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_final(digest, ctx); ++ if (rc != SSH_OK) { ++ return -1; ++ } + if (requested_len < output_len + crypto->digest_len) { + memcpy(output + output_len, digest, requested_len - output_len); + } else { +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 5f3917ba..45f45a9e 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -126,26 +126,46 @@ SHACTX sha1_init(void) + return c; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) ++void sha1_ctx_free(SHACTX c) + { +- EVP_DigestUpdate(c, data, len); ++ EVP_MD_CTX_destroy(c); + } + +-void sha1_final(unsigned char *md, SHACTX c) ++int sha1_update(SHACTX c, const void *data, unsigned long len) ++{ ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha1_final(unsigned char *md, SHACTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) ++int sha1(const unsigned char *digest, int len, unsigned char *hash) + { + SHACTX c = sha1_init(); +- if (c != NULL) { +- sha1_update(c, digest, len); +- sha1_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha1_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha1_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha1_final(hash, c); + } + + SHA256CTX sha256_init(void) +@@ -164,26 +184,46 @@ SHA256CTX sha256_init(void) + return c; + } + +-void sha256_update(SHA256CTX c, const void *data, unsigned long len) ++void sha256_ctx_free(SHA256CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha256_update(SHA256CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256_final(unsigned char *md, SHA256CTX c) ++int sha256_final(unsigned char *md, SHA256CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash) ++int sha256(const unsigned char *digest, int len, unsigned char *hash) + { + SHA256CTX c = sha256_init(); +- if (c != NULL) { +- sha256_update(c, digest, len); +- sha256_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha256_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha256_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha256_final(hash, c); + } + + SHA384CTX sha384_init(void) +@@ -202,26 +242,47 @@ SHA384CTX sha384_init(void) + return c; + } + +-void sha384_update(SHA384CTX c, const void *data, unsigned long len) ++void ++sha384_ctx_free(SHA384CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha384_update(SHA384CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384_final(unsigned char *md, SHA384CTX c) ++int sha384_final(unsigned char *md, SHA384CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) ++int sha384(const unsigned char *digest, int len, unsigned char *hash) + { + SHA384CTX c = sha384_init(); +- if (c != NULL) { +- sha384_update(c, digest, len); +- sha384_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha384_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha384_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha384_final(hash, c); + } + + SHA512CTX sha512_init(void) +@@ -240,26 +301,46 @@ SHA512CTX sha512_init(void) + return c; + } + +-void sha512_update(SHA512CTX c, const void *data, unsigned long len) ++void sha512_ctx_free(SHA512CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha512_update(SHA512CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512_final(unsigned char *md, SHA512CTX c) ++int sha512_final(unsigned char *md, SHA512CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) ++int sha512(const unsigned char *digest, int len, unsigned char *hash) + { + SHA512CTX c = sha512_init(); +- if (c != NULL) { +- sha512_update(c, digest, len); +- sha512_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; ++ } ++ rc = sha512_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha512_ctx_free(c); ++ return SSH_ERROR; + } ++ return sha512_final(hash, c); + } + + MD5CTX md5_init(void) +@@ -278,17 +359,30 @@ MD5CTX md5_init(void) + return c; + } + +-void md5_update(MD5CTX c, const void *data, unsigned long len) ++void md5_ctx_free(MD5CTX c) + { +- EVP_DigestUpdate(c, data, len); ++ EVP_MD_CTX_destroy(c); + } + +-void md5_final(unsigned char *md, MD5CTX c) ++int md5_update(MD5CTX c, const void *data, unsigned long len) ++{ ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int md5_final(unsigned char *md, MD5CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + +diff --git a/src/libgcrypt.c b/src/libgcrypt.c +index 49488793..d1332af3 100644 +--- a/src/libgcrypt.c ++++ b/src/libgcrypt.c +@@ -68,18 +68,35 @@ SHACTX sha1_init(void) { + return ctx; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha1_ctx_free(SHACTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha1_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha1_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); +- gcry_md_close(c); ++int sha1_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) { ++int sha1(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); ++ return SSH_OK; + } + + +@@ -90,18 +107,35 @@ SHA256CTX sha256_init(void) { + return ctx; + } + +-void sha256_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha256_ctx_free(SHA256CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha256_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha256_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); +- gcry_md_close(c); ++int sha256_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA256_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash){ ++int sha256(const unsigned char *digest, int len, unsigned char *hash){ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); ++ return SSH_OK; + } + + SHA384CTX sha384_init(void) { +@@ -111,18 +145,35 @@ SHA384CTX sha384_init(void) { + return ctx; + } + +-void sha384_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha384_ctx_free(SHA384CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha384_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha384_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); +- gcry_md_close(c); ++int sha384_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA384_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) { ++int sha384(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); ++ return SSH_OK; + } + + SHA512CTX sha512_init(void) { +@@ -132,18 +183,35 @@ SHA512CTX sha512_init(void) { + return ctx; + } + +-void sha512_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha512_ctx_free(SHA512CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha512_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha512_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); +- gcry_md_close(c); ++int sha512_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA512_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) { ++int sha512(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); ++ return SSH_OK; + } + + MD5CTX md5_init(void) { +@@ -153,14 +221,30 @@ MD5CTX md5_init(void) { + return c; + } + +-void md5_update(MD5CTX c, const void *data, unsigned long len) { ++void ++md5_ctx_free(MD5CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int md5_update(MD5CTX c, const void *data, unsigned long len) { + gcry_md_write(c,data,len); ++ return SSH_OK; + } + +-void md5_final(unsigned char *md, MD5CTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); +- gcry_md_close(c); ++int md5_final(unsigned char *md, MD5CTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, MD5_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c +index f37a6a6d..e9a2b8e5 100644 +--- a/src/libmbedcrypto.c ++++ b/src/libmbedcrypto.c +@@ -82,25 +82,46 @@ SHACTX sha1_init(void) + return ctx; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) ++void ++sha1_ctx_free(SHACTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha1_final(unsigned char *md, SHACTX c) ++int sha1_update(SHACTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) ++int sha1_final(unsigned char *md, SHACTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha1_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha1(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; + } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + SHA256CTX sha256_init(void) +@@ -136,25 +157,46 @@ SHA256CTX sha256_init(void) + return ctx; + } + +-void sha256_update(SHA256CTX c, const void *data, unsigned long len) ++void ++sha256_ctx_free(SHA256CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha256_final(unsigned char *md, SHA256CTX c) ++int sha256_update(SHA256CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha256_final(unsigned char *md, SHA256CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha256_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash) ++int sha256(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; ++ } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; + } ++ return SSH_OK; + } + + SHA384CTX sha384_init(void) +@@ -190,25 +232,46 @@ SHA384CTX sha384_init(void) + return ctx; + } + +-void sha384_update(SHA384CTX c, const void *data, unsigned long len) ++void ++sha384_ctx_free(SHA384CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha384_final(unsigned char *md, SHA384CTX c) ++int sha384_update(SHA384CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) ++int sha384_final(unsigned char *md, SHA384CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha384_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha384(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; ++ } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; + } ++ return SSH_OK; + } + + SHA512CTX sha512_init(void) +@@ -243,25 +306,46 @@ SHA512CTX sha512_init(void) + return ctx; + } + +-void sha512_update(SHA512CTX c, const void *data, unsigned long len) ++void ++sha512_ctx_free(SHA512CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha512_final(unsigned char *md, SHA512CTX c) ++int sha512_update(SHA512CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) ++int sha512_final(unsigned char *md, SHA512CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha512_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha512(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; + } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + MD5CTX md5_init(void) +@@ -296,16 +380,30 @@ MD5CTX md5_init(void) + return ctx; + } + ++void ++md5_ctx_free(MD5CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} + +-void md5_update(MD5CTX c, const void *data, unsigned long len) { +- mbedtls_md_update(c, data, len); ++int md5_update(MD5CTX c, const void *data, unsigned long len) ++{ ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void md5_final(unsigned char *md, MD5CTX c) ++int md5_final(unsigned char *md, MD5CTX c) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_finish(c, md); ++ md5_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/session.c b/src/session.c +index 0cf59e99..349373aa 100644 +--- a/src/session.c ++++ b/src/session.c +@@ -1001,7 +1001,18 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { +- ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); ++ ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); ++ return SSH_ERROR; ++ } ++ ++ rc = ssh_get_server_publickey(session, &pubkey); ++ if (rc != SSH_OK) { ++ return SSH_ERROR; ++ } ++ ++ rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); ++ ssh_key_free(pubkey); ++ if (rc != SSH_OK) { + return SSH_ERROR; + } + +@@ -1016,25 +1027,21 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) + return SSH_ERROR; + } + +- rc = ssh_get_server_publickey(session, &pubkey); ++ rc = md5_update(ctx, ++ ssh_string_data(pubkey_blob), ++ ssh_string_len(pubkey_blob)); + if (rc != SSH_OK) { +- md5_final(h, ctx); ++ md5_ctx_free(ctx); + SAFE_FREE(h); +- return SSH_ERROR; ++ return rc; + } +- +- rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); +- ssh_key_free(pubkey); ++ SSH_STRING_FREE(pubkey_blob); ++ rc = md5_final(h, ctx); + if (rc != SSH_OK) { +- md5_final(h, ctx); + SAFE_FREE(h); +- return SSH_ERROR; ++ return rc; + } + +- md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); +- SSH_STRING_FREE(pubkey_blob); +- md5_final(h, ctx); +- + *hash = h; + + return MD5_DIGEST_LEN; +@@ -1153,8 +1160,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- sha1_final(h, ctx); ++ rc = sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ sha1_ctx_free(ctx); ++ goto out; ++ } ++ rc = sha1_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = SHA_DIGEST_LEN; + } +@@ -1176,8 +1192,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- sha256_final(h, ctx); ++ rc = sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ sha256_ctx_free(ctx); ++ goto out; ++ } ++ rc = sha256_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = SHA256_DIGEST_LEN; + } +@@ -1207,8 +1232,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- md5_final(h, ctx); ++ rc = md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ md5_ctx_free(ctx); ++ goto out; ++ } ++ rc = md5_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = MD5_DIGEST_LEN; + } +-- +2.41.0 + + +From 9276027c687723886e8277b77061464303845831 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 13:35:14 +0100 +Subject: [PATCH 4/5] CVE-2023-6918: kdf: Detect context init failures + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + src/kdf.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/src/kdf.c b/src/kdf.c +index 90f6e9f3..b08f0b2f 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -61,20 +61,32 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); ++ if (ctx->ctx.sha1_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); ++ if (ctx->ctx.sha256_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA384: + ctx->ctx.sha384_ctx = sha384_init(); ++ if (ctx->ctx.sha384_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA512: + ctx->ctx.sha512_ctx = sha512_init(); ++ if (ctx->ctx.sha512_ctx == NULL) { ++ goto err; ++ } + return ctx; +- default: +- SAFE_FREE(ctx); +- return NULL; + } ++err: ++ SAFE_FREE(ctx); ++ return NULL; + } + + static void ssh_mac_ctx_free(ssh_mac_ctx ctx) +-- +2.41.0 + + +From aef4a470003bdede61fa17c22418d25ccedaa983 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 15:39:12 +0100 +Subject: [PATCH 5/5] CVE-2023-6918: tests: Code coverage for + ssh_get_pubkey_hash() + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + tests/client/torture_session.c | 35 ++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c +index b5ed7a62..abe458d0 100644 +--- a/tests/client/torture_session.c ++++ b/tests/client/torture_session.c +@@ -118,12 +118,47 @@ static void torture_channel_read_error(void **state) { + ssh_channel_free(channel); + } + ++static void torture_pubkey_hash(void **state) ++{ ++ struct torture_state *s = *state; ++ ssh_session session = s->ssh.session; ++ char *hash = NULL; ++ char *hexa = NULL; ++ int rc = 0; ++ ++ /* bad arguments */ ++ rc = ssh_get_pubkey_hash(session, NULL); ++ assert_int_equal(rc, SSH_ERROR); ++ ++ rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash); ++ assert_int_equal(rc, SSH_ERROR); ++ ++ /* deprecated, but should be covered by tests! */ ++ rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash); ++ if (ssh_fips_mode()) { ++ /* When in FIPS mode, expect the call to fail */ ++ assert_int_equal(rc, SSH_ERROR); ++ } else { ++ assert_int_equal(rc, MD5_DIGEST_LEN); ++ ++ hexa = ssh_get_hexa((unsigned char *)hash, rc); ++ SSH_STRING_FREE_CHAR(hash); ++ assert_string_equal(hexa, ++ "ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b"); ++ ++ SSH_STRING_FREE_CHAR(hexa); ++ } ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_channel_read_error, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_pubkey_hash, ++ session_setup, ++ session_teardown), + }; + + ssh_init(); +-- +2.41.0 + diff --git a/SOURCES/auth_bypass.patch b/SOURCES/auth_bypass.patch new file mode 100644 index 0000000..8b91601 --- /dev/null +++ b/SOURCES/auth_bypass.patch @@ -0,0 +1,86 @@ +diff --color -ru ../libssh-0.9.6/src/pki_crypto.c ./src/pki_crypto.c +--- ../libssh-0.9.6/src/pki_crypto.c 2023-04-27 12:59:08.463259052 +0200 ++++ ./src/pki_crypto.c 2023-04-27 13:05:24.020610873 +0200 +@@ -2291,8 +2291,12 @@ + unsigned char *raw_sig_data = NULL; + unsigned int raw_sig_len; + ++ /* Function return code ++ * Do not change this variable throughout the function until the signature ++ * is successfully verified! ++ */ + int rc = SSH_ERROR; +- int evp_rc; ++ int ok; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL || (signature->raw_sig == NULL +@@ -2307,8 +2311,8 @@ + } + + /* Check if public key and hash type are compatible */ +- rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); +- if (rc != SSH_OK) { ++ ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); ++ if (ok != SSH_OK) { + return SSH_ERROR; + } + +@@ -2351,8 +2355,8 @@ + } + + /* Verify the signature */ +- evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); +- if (evp_rc != 1){ ++ ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); ++ if (ok != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); +@@ -2360,35 +2364,31 @@ + } + + #ifdef HAVE_OPENSSL_EVP_DIGESTVERIFY +- evp_rc = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); ++ ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + #else +- evp_rc = EVP_DigestVerifyUpdate(ctx, input, input_len); +- if (evp_rc != 1) { ++ ok = EVP_DigestVerifyUpdate(ctx, input, input_len); ++ if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyUpdate() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + +- evp_rc = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); ++ ok = EVP_DigestVerifyFinal(ctx, raw_sig_data, raw_sig_len); + #endif +- if (evp_rc == 1) { +- SSH_LOG(SSH_LOG_TRACE, "Signature valid"); +- rc = SSH_OK; +- } else { ++ if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Signature invalid: %s", + ERR_error_string(ERR_get_error(), NULL)); +- rc = SSH_ERROR; ++ goto out; + } + ++ SSH_LOG(SSH_LOG_TRACE, "Signature valid"); ++ rc = SSH_OK; ++ + out: +- if (ctx != NULL) { +- EVP_MD_CTX_free(ctx); +- } +- if (pkey != NULL) { +- EVP_PKEY_free(pkey); +- } ++ EVP_MD_CTX_free(ctx); ++ EVP_PKEY_free(pkey); + return rc; + } + diff --git a/SOURCES/covscan23.patch b/SOURCES/covscan23.patch new file mode 100644 index 0000000..a79658f --- /dev/null +++ b/SOURCES/covscan23.patch @@ -0,0 +1,228 @@ +diff --color -ru ../libssh-0.9.6/src/buffer.c ./src/buffer.c +--- ../libssh-0.9.6/src/buffer.c 2023-05-03 11:53:48.710217753 +0200 ++++ ./src/buffer.c 2023-05-03 11:58:21.995200990 +0200 +@@ -747,7 +747,8 @@ + */ + int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) + { +- if (buffer->pos + len < len || buffer->pos + len > buffer->used) { ++ if (buffer == NULL || buffer->pos + len < len || ++ buffer->pos + len > buffer->used) { + return SSH_ERROR; + } + +diff --color -ru ../libssh-0.9.6/src/gssapi.c ./src/gssapi.c +--- ../libssh-0.9.6/src/gssapi.c 2023-05-03 11:53:48.732217993 +0200 ++++ ./src/gssapi.c 2023-05-03 11:58:21.976200782 +0200 +@@ -437,11 +437,18 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ ssh_auth_reply_default(session, 0); ++ ssh_gssapi_free(session); ++ session->gssapi = NULL; ++ return SSH_PACKET_USED; ++ } + ssh_packet_send(session); + } + +@@ -846,6 +853,7 @@ + } + + SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ ++ int rc; + ssh_string oid_s; + gss_uint32 maj_stat, min_stat; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; +@@ -897,11 +905,15 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ goto error; ++ } + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } +@@ -963,6 +975,7 @@ + } + + SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ ++ int rc; + ssh_string token; + char *hexa; + OM_uint32 maj_stat, min_stat; +@@ -1015,11 +1028,15 @@ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); +- ssh_buffer_pack(session->out_buffer, +- "bdP", +- SSH2_MSG_USERAUTH_GSSAPI_TOKEN, +- output_token.length, +- (size_t)output_token.length, output_token.value); ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bdP", ++ SSH2_MSG_USERAUTH_GSSAPI_TOKEN, ++ output_token.length, ++ (size_t)output_token.length, output_token.value); ++ if (rc != SSH_OK) { ++ ssh_set_error_oom(session); ++ goto error; ++ } + ssh_packet_send(session); + } + +diff --color -ru ../libssh-0.9.6/src/options.c ./src/options.c +--- ../libssh-0.9.6/src/options.c 2021-08-26 14:27:42.000000000 +0200 ++++ ./src/options.c 2023-05-03 11:58:22.000201044 +0200 +@@ -547,7 +547,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { ++ SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); + SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + if (i <= 0) { +@@ -743,7 +745,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { ++ SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + if (i < 0) { +@@ -1818,7 +1822,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { +- SAFE_FREE(q); ++ SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); ++ SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + +@@ -1845,7 +1851,9 @@ + } + i = strtol(q, &p, 10); + if (q == p) { +- SAFE_FREE(q); ++ SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); ++ SAFE_FREE(q); ++ return -1; + } + SAFE_FREE(q); + +diff --color -ru ../libssh-0.9.6/src/pki_container_openssh.c ./src/pki_container_openssh.c +--- ../libssh-0.9.6/src/pki_container_openssh.c 2023-05-03 11:53:48.713217785 +0200 ++++ ./src/pki_container_openssh.c 2023-05-03 11:58:21.976200782 +0200 +@@ -630,7 +630,11 @@ + goto error; + } + +- ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); ++ rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(kdf_buf); ++ goto error; ++ } + kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); + if (kdf_options == NULL){ + SSH_BUFFER_FREE(kdf_buf); +diff --color -ru ../libssh-0.9.6/tests/unittests/torture_options.c ./tests/unittests/torture_options.c +--- ../libssh-0.9.6/tests/unittests/torture_options.c 2021-08-26 14:27:42.000000000 +0200 ++++ ./tests/unittests/torture_options.c 2023-05-03 11:59:21.726853027 +0200 +@@ -311,6 +311,7 @@ + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); ++ assert_int_not_equal(session->opts.port, 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +@@ -853,6 +854,26 @@ + ssh_free(new); + } + ++static void torture_options_set_verbosity (void **state) ++{ ++ ssh_session session = *state; ++ int rc, new_level; ++ ++ rc = ssh_options_set(session, ++ SSH_OPTIONS_LOG_VERBOSITY_STR, ++ "3"); ++ assert_int_equal(rc, SSH_OK); ++ new_level = ssh_get_log_level(); ++ assert_int_equal(new_level, SSH_LOG_PACKET); ++ ++ rc = ssh_options_set(session, ++ SSH_OPTIONS_LOG_VERBOSITY_STR, ++ "datsun"); ++ assert_int_equal(rc, -1); ++ new_level = ssh_get_log_level(); ++ assert_int_not_equal(new_level, 0); ++} ++ + #ifdef WITH_SERVER + const char template[] = "temp_dir_XXXXXX"; + +@@ -1107,6 +1128,10 @@ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 23); ++ ++ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "twentythree"); ++ assert_int_equal(rc, -1); ++ assert_int_not_equal(bind->bindport, 0); + } + + static void torture_bind_options_log_verbosity(void **state) +@@ -1156,6 +1181,11 @@ + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + ++ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "verbosity"); ++ assert_int_equal(rc, -1); ++ new_level = ssh_get_log_level(); ++ assert_int_not_equal(new_level, 0); ++ + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); + } +@@ -1643,6 +1673,7 @@ + cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match, + setup, teardown), ++ cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), + }; + + #ifdef WITH_SERVER diff --git a/SOURCES/fix_tests.patch b/SOURCES/fix_tests.patch new file mode 100644 index 0000000..084fb49 --- /dev/null +++ b/SOURCES/fix_tests.patch @@ -0,0 +1,668 @@ +diff --color -ru ../libssh-0.9.6/examples/sshnetcat.c ./examples/sshnetcat.c +--- ../libssh-0.9.6/examples/sshnetcat.c 2021-08-26 14:27:42.000000000 +0200 ++++ ./examples/sshnetcat.c 2023-05-02 10:36:00.793381735 +0200 +@@ -233,9 +233,10 @@ + } + + void cleanup_pcap(void); +-void cleanup_pcap(){ ++void cleanup_pcap(void) ++{ + ssh_pcap_file_free(pcap); +- pcap=NULL; ++ pcap = NULL; + } + #endif + +diff --color -ru ../libssh-0.9.6/src/init.c ./src/init.c +--- ../libssh-0.9.6/src/init.c 2021-03-15 08:11:33.000000000 +0100 ++++ ./src/init.c 2023-05-02 10:36:00.793381735 +0200 +@@ -269,7 +269,7 @@ + * + * @see ssh_init() + */ +-bool is_ssh_initialized() { ++bool is_ssh_initialized(void) { + + bool is_initialized = false; + +diff --color -ru ../libssh-0.9.6/tests/client/torture_auth.c ./tests/client/torture_auth.c +--- ../libssh-0.9.6/tests/client/torture_auth.c 2021-08-26 14:27:42.000000000 +0200 ++++ ./tests/client/torture_auth.c 2023-05-02 10:36:00.815381960 +0200 +@@ -200,7 +200,8 @@ + assert_non_null(ssh_agent_pidfile); + + /* kill agent pid */ +- torture_terminate_process(ssh_agent_pidfile); ++ rc = torture_terminate_process(ssh_agent_pidfile); ++ assert_return_code(rc, errno); + + unlink(ssh_agent_pidfile); + +@@ -551,6 +552,7 @@ + + static void torture_auth_agent_cert(void **state) + { ++#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; +@@ -570,6 +572,7 @@ + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } ++#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +@@ -577,6 +580,7 @@ + + static void torture_auth_agent_cert_nonblocking(void **state) + { ++#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; +@@ -596,6 +600,7 @@ + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } ++#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ + + torture_auth_agent_nonblocking(state); + } +diff --color -ru ../libssh-0.9.6/tests/client/torture_rekey.c ./tests/client/torture_rekey.c +--- ../libssh-0.9.6/tests/client/torture_rekey.c 2023-04-28 17:26:41.472315318 +0200 ++++ ./tests/client/torture_rekey.c 2023-05-02 10:36:00.805381857 +0200 +@@ -38,6 +38,8 @@ + #include + #include + ++#define KEX_RETRY 32 ++ + static uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ + + static int sshd_setup(void **state) +@@ -190,10 +192,11 @@ + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +- /* send ignore packets of up to 1KB to trigger rekey */ ++ /* send ignore packets of up to 1KB to trigger rekey. Send little bit more ++ * to make sure it completes with all different ciphers */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); +- for (i = 0; i < 16; i++) { ++ for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } +@@ -496,9 +499,15 @@ + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); +- for (i = 0; i < 20; i++) { ++ for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); +- ssh_handle_packets(s->ssh.session, 50); ++ ssh_handle_packets(s->ssh.session, 100); ++ ++ c = s->ssh.session->current_crypto; ++ /* SHA256 len */ ++ if (c->digest_len != 32) { ++ break; ++ } + } + + /* The rekey limit was restored in the new crypto to the same value */ +@@ -568,9 +577,15 @@ + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); +- for (i = 0; i < 25; i++) { ++ for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); +- ssh_handle_packets(s->ssh.session, 50); ++ ssh_handle_packets(s->ssh.session, 100); ++ ++ c = s->ssh.session->current_crypto; ++ /* SHA256 len */ ++ if (c->digest_len != 32) { ++ break; ++ } + } + + /* Check that the secret hash is different than initially */ +diff --color -ru ../libssh-0.9.6/tests/CMakeLists.txt ./tests/CMakeLists.txt +--- ../libssh-0.9.6/tests/CMakeLists.txt 2021-08-26 14:27:42.000000000 +0200 ++++ ./tests/CMakeLists.txt 2023-05-02 10:32:03.964511860 +0200 +@@ -153,6 +153,17 @@ + execute_process(COMMAND ${ID_EXECUTABLE} -u OUTPUT_VARIABLE LOCAL_UID OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + ++ find_program(TIMEOUT_EXECUTABLE ++ NAME ++ timeout ++ PATHS ++ /bin ++ /usr/bin ++ /usr/local/bin) ++ if (TIMEOUT_EXECUTABLE) ++ set(WITH_TIMEOUT "1") ++ endif() ++ + # chroot_wrapper + add_library(chroot_wrapper SHARED chroot_wrapper.c) + set(CHROOT_WRAPPER_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chroot_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) +diff --color -ru ../libssh-0.9.6/tests/pkd/pkd_keyutil.c ./tests/pkd/pkd_keyutil.c +--- ../libssh-0.9.6/tests/pkd/pkd_keyutil.c 2021-03-15 08:11:33.000000000 +0100 ++++ ./tests/pkd/pkd_keyutil.c 2023-05-02 10:36:00.793381735 +0200 +@@ -22,7 +22,7 @@ + #include "pkd_keyutil.h" + #include "pkd_util.h" + +-void setup_rsa_key() { ++void setup_rsa_key(void) { + int rc = 0; + if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " +@@ -31,7 +31,7 @@ + assert_int_equal(rc, 0); + } + +-void setup_ed25519_key() { ++void setup_ed25519_key(void) { + int rc = 0; + if (access(LIBSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " +@@ -41,7 +41,7 @@ + } + + #ifdef HAVE_DSA +-void setup_dsa_key() { ++void setup_dsa_key(void) { + int rc = 0; + if (access(LIBSSH_DSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t dsa -q -N \"\" -f " +@@ -51,7 +51,7 @@ + } + #endif + +-void setup_ecdsa_keys() { ++void setup_ecdsa_keys(void) { + int rc = 0; + + if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) { +@@ -71,27 +71,27 @@ + } + } + +-void cleanup_rsa_key() { ++void cleanup_rsa_key(void) { + cleanup_key(LIBSSH_RSA_TESTKEY); + } + +-void cleanup_ed25519_key() { ++void cleanup_ed25519_key(void) { + cleanup_key(LIBSSH_ED25519_TESTKEY); + } + + #ifdef HAVE_DSA +-void cleanup_dsa_key() { ++void cleanup_dsa_key(void) { + cleanup_key(LIBSSH_DSA_TESTKEY); + } + #endif + +-void cleanup_ecdsa_keys() { ++void cleanup_ecdsa_keys(void) { + cleanup_key(LIBSSH_ECDSA_256_TESTKEY); + cleanup_key(LIBSSH_ECDSA_384_TESTKEY); + cleanup_key(LIBSSH_ECDSA_521_TESTKEY); + } + +-void setup_openssh_client_keys() { ++void setup_openssh_client_keys(void) { + int rc = 0; + + if (access(OPENSSH_CA_TESTKEY, F_OK) != 0) { +@@ -184,7 +184,7 @@ + } + } + +-void cleanup_openssh_client_keys() { ++void cleanup_openssh_client_keys(void) { + cleanup_key(OPENSSH_CA_TESTKEY); + cleanup_key(OPENSSH_RSA_TESTKEY); + cleanup_file(OPENSSH_RSA_TESTKEY "-sha256-cert.pub"); +@@ -199,7 +199,7 @@ + } + } + +-void setup_dropbear_client_rsa_key() { ++void setup_dropbear_client_rsa_key(void) { + int rc = 0; + if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " +@@ -208,6 +208,6 @@ + assert_int_equal(rc, 0); + } + +-void cleanup_dropbear_client_rsa_key() { ++void cleanup_dropbear_client_rsa_key(void) { + unlink(DROPBEAR_RSA_TESTKEY); + } +diff --color -ru ../libssh-0.9.6/tests/server/torture_server_config.c ./tests/server/torture_server_config.c +--- ../libssh-0.9.6/tests/server/torture_server_config.c 2021-08-26 14:27:42.000000000 +0200 ++++ ./tests/server/torture_server_config.c 2023-05-02 10:36:00.815381960 +0200 +@@ -285,9 +285,7 @@ + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); +- if (rc != 0) { +- fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); +- } ++ assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + +@@ -513,6 +511,12 @@ + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { ++ char *cmp = strstr(OPENSSH_CIPHERS, tokens->tokens[j]); ++ if (cmp == NULL) { ++ /* This cipher is not supported by the OpenSSH. Skip it */ ++ j++; ++ continue; ++ } + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", +diff --color -ru ../libssh-0.9.6/tests/tests_config.h.cmake ./tests/tests_config.h.cmake +--- ../libssh-0.9.6/tests/tests_config.h.cmake 2021-08-26 14:27:42.000000000 +0200 ++++ ./tests/tests_config.h.cmake 2023-05-02 10:32:03.964511860 +0200 +@@ -66,4 +66,6 @@ + + #cmakedefine NC_EXECUTABLE "${NC_EXECUTABLE}" + #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" +-#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +\ No newline at end of file ++#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" ++#cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} ++#cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" +diff --color -ru ../libssh-0.9.6/tests/torture.c ./tests/torture.c +--- ../libssh-0.9.6/tests/torture.c 2021-08-26 14:27:44.000000000 +0200 ++++ ./tests/torture.c 2023-05-02 10:36:00.815381960 +0200 +@@ -51,6 +51,7 @@ + #include "torture.h" + #include "torture_key.h" + #include "libssh/misc.h" ++#include "libssh/token.h" + + #define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" + /* socket wrapper IPv6 prefix fd00::5357:5fxx */ +@@ -250,8 +251,12 @@ + + rc = kill(pid, 0); + if (rc != 0) { +- is_running = 0; +- break; ++ /* Process not found */ ++ if (errno == ESRCH) { ++ is_running = 0; ++ rc = 0; ++ break; ++ } + } + } + +@@ -260,7 +265,7 @@ + "WARNING: The process with pid %u is still running!\n", pid); + } + +- return 0; ++ return rc; + } + + ssh_session torture_ssh_session(struct torture_state *s, +@@ -611,6 +616,112 @@ + *state = s; + } + ++/** ++ * @brief Create a libssh server configuration file ++ * ++ * It is expected the socket directory to be already created before by calling ++ * torture_setup_socket_dir(). The created configuration file will be stored in ++ * the socket directory and the srv_config pointer in the state will be ++ * initialized. ++ * ++ * @param[in] state A pointer to a pointer to an initialized torture_state ++ * structure ++ */ ++void torture_setup_create_libssh_config(void **state) ++{ ++ struct torture_state *s = *state; ++ char ed25519_hostkey[1024] = {0}; ++#ifdef HAVE_DSA ++ char dsa_hostkey[1024]; ++#endif /* HAVE_DSA */ ++ char rsa_hostkey[1024]; ++ char ecdsa_hostkey[1024]; ++ char sshd_config[2048]; ++ char sshd_path[1024]; ++ const char *additional_config = NULL; ++ struct stat sb; ++ const char config_string[]= ++ "LogLevel DEBUG3\n" ++ "Port 22\n" ++ "ListenAddress 127.0.0.10\n" ++ "%s %s\n" ++ "%s %s\n" ++ "%s %s\n" ++#ifdef HAVE_DSA ++ "%s %s\n" ++#endif /* HAVE_DSA */ ++ "%s\n"; /* The space for test-specific options */ ++ bool written = false; ++ int rc; ++ ++ assert_non_null(s->socket_dir); ++ ++ snprintf(sshd_path, ++ sizeof(sshd_path), ++ "%s/sshd", ++ s->socket_dir); ++ ++ rc = lstat(sshd_path, &sb); ++ if (rc == 0 ) { /* The directory is already in place */ ++ written = true; ++ } ++ ++ if (!written) { ++ rc = mkdir(sshd_path, 0755); ++ assert_return_code(rc, errno); ++ } ++ ++ snprintf(ed25519_hostkey, ++ sizeof(ed25519_hostkey), ++ "%s/sshd/ssh_host_ed25519_key", ++ s->socket_dir); ++ ++ snprintf(rsa_hostkey, ++ sizeof(rsa_hostkey), ++ "%s/sshd/ssh_host_rsa_key", ++ s->socket_dir); ++ ++ snprintf(ecdsa_hostkey, ++ sizeof(ecdsa_hostkey), ++ "%s/sshd/ssh_host_ecdsa_key", ++ s->socket_dir); ++ ++#ifdef HAVE_DSA ++ snprintf(dsa_hostkey, ++ sizeof(dsa_hostkey), ++ "%s/sshd/ssh_host_dsa_key", ++ s->socket_dir); ++#endif /* HAVE_DSA */ ++ ++ if (!written) { ++ torture_write_file(ed25519_hostkey, ++ torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); ++ torture_write_file(rsa_hostkey, ++ torture_get_testkey(SSH_KEYTYPE_RSA, 0)); ++ torture_write_file(ecdsa_hostkey, ++ torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); ++#ifdef HAVE_DSA ++ torture_write_file(dsa_hostkey, ++ torture_get_testkey(SSH_KEYTYPE_DSS, 0)); ++#endif /* HAVE_DSA */ ++ } ++ ++ additional_config = (s->srv_additional_config != NULL ? ++ s->srv_additional_config : ""); ++ ++ snprintf(sshd_config, sizeof(sshd_config), ++ config_string, ++ "HostKey", ed25519_hostkey, ++ "HostKey", rsa_hostkey, ++ "HostKey", ecdsa_hostkey, ++#ifdef HAVE_DSA ++ "HostKey", dsa_hostkey, ++#endif /* HAVE_DSA */ ++ additional_config); ++ ++ torture_write_file(s->srv_config, sshd_config); ++} ++ + static void torture_setup_create_sshd_config(void **state, bool pam) + { + struct torture_state *s = *state; +@@ -856,21 +967,140 @@ + return 1; + } + +-void torture_setup_sshd_server(void **state, bool pam) ++/** ++ * @brief Run a libssh based server under timeout. ++ * ++ * It is expected that the socket directory and libssh configuration file were ++ * already created before by calling torture_setup_socket_dir() and ++ * torture_setup_create_libssh_config() (or alternatively setup the state with ++ * the correct values). ++ * ++ * @param[in] state The content of the address pointed by this variable must be ++ * a pointer to an initialized instance of torture_state ++ * structure; it can be obtained by calling ++ * torture_setup_socket_dir() and ++ * torture_setup_create_libssh_config(). ++ * @param[in] server_path The path to the server executable. ++ * ++ * @note This function will use the state->srv_additional_config field as ++ * additional command line option used when starting the server instead of extra ++ * configuration file options. ++ * */ ++void torture_setup_libssh_server(void **state, const char *server_path) + { + struct torture_state *s; +- char sshd_start_cmd[1024]; ++ char start_cmd[1024]; ++ char timeout_cmd[512]; ++ char env[1024]; ++ char extra_options[1024]; + int rc; ++ char *ld_preload = NULL; ++ const char *force_fips = NULL; + +- torture_setup_socket_dir(state); +- torture_setup_create_sshd_config(state, pam); ++ struct ssh_tokens_st *env_tokens; ++ struct ssh_tokens_st *arg_tokens; ++ ++ pid_t pid; ++ ssize_t printed; ++ ++ s = *state; ++ ++ /* Get all the wrapper libraries to be pre-loaded */ ++ ld_preload = getenv("LD_PRELOAD"); ++ ++ if (s->srv_additional_config != NULL) { ++ printed = snprintf(extra_options, sizeof(extra_options), " %s ", ++ s->srv_additional_config); ++ if (printed < 0) { ++ fail_msg("Failed to print additional config!"); ++ } ++ } else { ++ printed = snprintf(extra_options, sizeof(extra_options), " "); ++ if (printed < 0) { ++ fail_msg("Failed to print empty additional config!"); ++ } ++ } ++ ++ if (ssh_fips_mode()) { ++ force_fips = "OPENSSL_FORCE_FIPS_MODE=1 "; ++ } else { ++ force_fips = ""; ++ } ++ ++ /* Write the environment setting */ ++ printed = snprintf(env, sizeof(env), ++ "SOCKET_WRAPPER_DIR=%s " ++ "SOCKET_WRAPPER_DEFAULT_IFACE=10 " ++ "LD_PRELOAD=%s " ++ "%s", ++ s->socket_dir, ld_preload, force_fips); ++ if (printed < 0) { ++ fail_msg("Failed to print env!"); ++ } ++ ++#ifdef WITH_TIMEOUT ++ snprintf(timeout_cmd, sizeof(timeout_cmd), ++ "%s %s ", TIMEOUT_EXECUTABLE, "5m"); ++#else ++ timeout_cmd[0] = '\0'; ++#endif ++ ++ /* Write the start command */ ++ printed = snprintf(start_cmd, sizeof(start_cmd), ++ "%s" ++ "%s -f%s -v4 -p22 -i%s -C%s%s%s", ++ timeout_cmd, ++ server_path, s->pcap_file, s->srv_pidfile, ++ s->srv_config, extra_options, TORTURE_SSH_SERVER); ++ if (printed < 0) { ++ fail_msg("Failed to print start command!"); ++ } ++ ++ pid = fork(); ++ switch(pid) { ++ case 0: ++ env_tokens = ssh_tokenize(env, ' '); ++ if (env_tokens == NULL || env_tokens->tokens == NULL) { ++ fail_msg("Failed to tokenize env!"); ++ } ++ ++ arg_tokens = ssh_tokenize(start_cmd, ' '); ++ if (arg_tokens == NULL || arg_tokens->tokens == NULL) { ++ ssh_tokens_free(env_tokens); ++ fail_msg("Failed to tokenize args!"); ++ } ++ ++ rc = execve(arg_tokens->tokens[0], (char **)arg_tokens->tokens, ++ (char **)env_tokens->tokens); ++ ++ /* execve returns only in case of error */ ++ ssh_tokens_free(env_tokens); ++ ssh_tokens_free(arg_tokens); ++ fail_msg("Error in execve: %s", strerror(errno)); ++ case -1: ++ fail_msg("Failed to fork!"); ++ default: ++ /* The parent continues the execution of the tests */ ++ setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); ++ unsetenv("PAM_WRAPPER"); ++ ++ /* Wait until the server is ready to accept connections */ ++ rc = torture_wait_for_daemon(15); ++ assert_int_equal(rc, 0); ++ break; ++ } ++} ++ ++static int torture_start_sshd_server(void **state) ++{ ++ struct torture_state *s = *state; ++ char sshd_start_cmd[1024]; ++ int rc; + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + +- s = *state; +- + snprintf(sshd_start_cmd, sizeof(sshd_start_cmd), + SSHD_EXECUTABLE " -r -f %s -E %s/sshd/daemon.log 2> %s/sshd/cwrap.log", + s->srv_config, s->socket_dir, s->socket_dir); +@@ -882,7 +1112,20 @@ + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ +- rc = torture_wait_for_daemon(5); ++ rc = torture_wait_for_daemon(15); ++ assert_int_equal(rc, 0); ++ ++ return SSH_OK; ++} ++ ++void torture_setup_sshd_server(void **state, bool pam) ++{ ++ int rc; ++ ++ torture_setup_socket_dir(state); ++ torture_setup_create_sshd_config(state, pam); ++ ++ rc = torture_start_sshd_server(state); + assert_int_equal(rc, 0); + } + +@@ -922,29 +1165,12 @@ + torture_reload_sshd_server(void **state) + { + struct torture_state *s = *state; +- pid_t pid; + int rc; + +- /* read the pidfile */ +- pid = torture_read_pidfile(s->srv_pidfile); +- assert_int_not_equal(pid, -1); +- +- kill(pid, SIGHUP); +- +- /* 10 ms */ +- usleep(10 * 1000); +- +- rc = kill(pid, 0); +- if (rc != 0) { +- fprintf(stderr, +- "ERROR: SSHD process %u died during reload!\n", pid); +- return SSH_ERROR; +- } ++ rc = torture_terminate_process(s->srv_pidfile); ++ assert_return_code(rc, errno); + +- /* Wait until the sshd is ready to accept connections */ +- rc = torture_wait_for_daemon(5); +- assert_int_equal(rc, 0); +- return SSH_OK; ++ return torture_start_sshd_server(state); + } + + /* @brief: Updates SSHD server configuration with more options and +@@ -980,9 +1206,7 @@ + int rc; + + rc = torture_terminate_process(s->srv_pidfile); +- if (rc != 0) { +- fprintf(stderr, "XXXXXX Failed to terminate sshd\n"); +- } ++ assert_return_code(rc, errno); + + torture_teardown_socket_dir(state); + } +diff --color -ru ../libssh-0.9.6/tests/torture.h ./tests/torture.h +--- ../libssh-0.9.6/tests/torture.h 2021-08-26 14:27:44.000000000 +0200 ++++ ./tests/torture.h 2023-05-02 10:32:03.964511860 +0200 +@@ -132,6 +132,10 @@ + + void torture_reset_config(ssh_session session); + ++void torture_setup_create_libssh_config(void **state); ++ ++void torture_setup_libssh_server(void **state, const char *server_path); ++ + /* + * This function must be defined in every unit test file. + */ diff --git a/SOURCES/null_dereference_rekey.patch b/SOURCES/null_dereference_rekey.patch new file mode 100644 index 0000000..505240b --- /dev/null +++ b/SOURCES/null_dereference_rekey.patch @@ -0,0 +1,1845 @@ +diff --color -ru ../libssh-0.9.6/include/libssh/curve25519.h ./include/libssh/curve25519.h +--- ../libssh-0.9.6/include/libssh/curve25519.h 2021-03-15 08:11:33.000000000 +0100 ++++ ./include/libssh/curve25519.h 2023-04-27 12:21:30.257820636 +0200 +@@ -48,6 +48,7 @@ + + + int ssh_client_curve25519_init(ssh_session session); ++void ssh_client_curve25519_remove_callbacks(ssh_session session); + + #ifdef WITH_SERVER + void ssh_server_curve25519_init(ssh_session session); +diff --color -ru ../libssh-0.9.6/include/libssh/dh-gex.h ./include/libssh/dh-gex.h +--- ../libssh-0.9.6/include/libssh/dh-gex.h 2021-03-15 08:11:33.000000000 +0100 ++++ ./include/libssh/dh-gex.h 2023-04-27 12:21:30.257820636 +0200 +@@ -24,6 +24,7 @@ + #define SRC_DH_GEX_H_ + + int ssh_client_dhgex_init(ssh_session session); ++void ssh_client_dhgex_remove_callbacks(ssh_session session); + + #ifdef WITH_SERVER + void ssh_server_dhgex_init(ssh_session session); +diff --color -ru ../libssh-0.9.6/include/libssh/dh.h ./include/libssh/dh.h +--- ../libssh-0.9.6/include/libssh/dh.h 2021-03-15 08:11:33.000000000 +0100 ++++ ./include/libssh/dh.h 2023-04-27 12:21:30.257820636 +0200 +@@ -63,8 +63,10 @@ + ssh_key ssh_dh_get_next_server_publickey(ssh_session session); + int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); ++int dh_handshake(ssh_session session); + + int ssh_client_dh_init(ssh_session session); ++void ssh_client_dh_remove_callbacks(ssh_session session); + #ifdef WITH_SERVER + void ssh_server_dh_init(ssh_session session); + #endif /* WITH_SERVER */ +diff --color -ru ../libssh-0.9.6/include/libssh/ecdh.h ./include/libssh/ecdh.h +--- ../libssh-0.9.6/include/libssh/ecdh.h 2021-03-15 08:11:33.000000000 +0100 ++++ ./include/libssh/ecdh.h 2023-04-27 12:21:30.257820636 +0200 +@@ -45,6 +45,7 @@ + extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; + /* Backend-specific functions. */ + int ssh_client_ecdh_init(ssh_session session); ++void ssh_client_ecdh_remove_callbacks(ssh_session session); + int ecdh_build_k(ssh_session session); + + #ifdef WITH_SERVER +diff --color -ru ../libssh-0.9.6/include/libssh/kex.h ./include/libssh/kex.h +--- ../libssh-0.9.6/include/libssh/kex.h 2021-03-15 08:11:33.000000000 +0100 ++++ ./include/libssh/kex.h 2023-04-27 12:21:30.257820636 +0200 +@@ -33,7 +33,7 @@ + + SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +-int ssh_send_kex(ssh_session session, int server_kex); ++int ssh_send_kex(ssh_session session); + void ssh_list_kex(struct ssh_kex_struct *kex); + int ssh_set_client_kex(ssh_session session); + int ssh_kex_select_methods(ssh_session session); +diff --color -ru ../libssh-0.9.6/include/libssh/session.h ./include/libssh/session.h +--- ../libssh-0.9.6/include/libssh/session.h 2021-08-26 14:27:42.000000000 +0200 ++++ ./include/libssh/session.h 2023-04-27 12:24:04.679475777 +0200 +@@ -75,6 +75,11 @@ + /* Client successfully authenticated */ + #define SSH_SESSION_FLAG_AUTHENTICATED 2 + ++/* The KEXINIT message can be sent first by either of the parties so this flag ++ * indicates that the message was already sent to make sure it is sent and avoid ++ * sending it twice during key exchange to simplify the state machine. */ ++#define SSH_SESSION_FLAG_KEXINIT_SENT 4 ++ + /* codes to use with ssh_handle_packets*() */ + /* Infinite timeout */ + #define SSH_TIMEOUT_INFINITE -1 +@@ -131,10 +136,8 @@ + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + +- ssh_string banner; /* that's the issue banner from +- the server */ +- char *discon_msg; /* disconnect message from +- the remote host */ ++ ssh_string banner; /* that's the issue banner from the server */ ++ char *discon_msg; /* disconnect message from the remote host */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; +@@ -158,25 +161,33 @@ + uint32_t current_method; + } auth; + ++ /* Sending this flag before key exchange to save one round trip during the ++ * key exchange. This might make sense on high-latency connections. ++ * So far internal only for testing. Usable only on the client side -- ++ * there is no key exchange method that would start with server message */ ++ bool send_first_kex_follows; + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will +- * be ignored. Once that packet has been received and ignored, +- * this field is cleared. ++ * be ignored on the receiving side. Once that packet has been received and ++ * ignored, this field is cleared. ++ * On the sending side, this is set after we got peer KEXINIT message and we ++ * need to resend the initial message of the negotiated KEX algorithm. + */ +- int first_kex_follows_guess_wrong; ++ bool first_kex_follows_guess_wrong; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; +- struct ssh_crypto_struct *next_crypto; /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ ++ /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ ++ struct ssh_crypto_struct *next_crypto; + + struct ssh_list *channels; /* linked list of channels */ + int maxchannel; + ssh_agent agent; /* ssh agent */ + +-/* keyb interactive data */ ++ /* keyboard interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + +@@ -193,7 +204,8 @@ + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ +- int (*ssh_message_callback)( struct ssh_session_struct *session, ssh_message msg, void *userdata); ++ int (*ssh_message_callback)(struct ssh_session_struct *session, ++ ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); +diff --color -ru ../libssh-0.9.6/src/client.c ./src/client.c +--- ../libssh-0.9.6/src/client.c 2023-04-27 12:22:39.797925403 +0200 ++++ ./src/client.c 2023-04-27 12:24:04.680475778 +0200 +@@ -243,10 +243,13 @@ + * @warning this function returning is no proof that DH handshake is + * completed + */ +-static int dh_handshake(ssh_session session) { +- ++int dh_handshake(ssh_session session) ++{ + int rc = SSH_AGAIN; + ++ SSH_LOG(SSH_LOG_TRACE, "dh_handshake_state = %d, kex_type = %d", ++ session->dh_handshake_state, session->next_crypto->kex_type); ++ + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + switch(session->next_crypto->kex_type){ +@@ -386,96 +389,102 @@ + { + int rc; + +- switch(session->session_state) { +- case SSH_SESSION_STATE_NONE: +- case SSH_SESSION_STATE_CONNECTING: +- break; +- case SSH_SESSION_STATE_SOCKET_CONNECTED: +- ssh_set_fd_towrite(session); +- ssh_send_banner(session, 0); +- +- break; +- case SSH_SESSION_STATE_BANNER_RECEIVED: +- if (session->serverbanner == NULL) { +- goto error; +- } +- set_status(session, 0.4f); +- SSH_LOG(SSH_LOG_DEBUG, +- "SSH server banner: %s", session->serverbanner); ++ SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); + +- /* Here we analyze the different protocols the server allows. */ +- rc = ssh_analyze_banner(session, 0); +- if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, +- "No version of SSH protocol usable (banner: %s)", +- session->serverbanner); +- goto error; +- } ++ switch (session->session_state) { ++ case SSH_SESSION_STATE_NONE: ++ case SSH_SESSION_STATE_CONNECTING: ++ break; ++ case SSH_SESSION_STATE_SOCKET_CONNECTED: ++ ssh_set_fd_towrite(session); ++ ssh_send_banner(session, 0); ++ ++ break; ++ case SSH_SESSION_STATE_BANNER_RECEIVED: ++ if (session->serverbanner == NULL) { ++ goto error; ++ } ++ set_status(session, 0.4f); ++ SSH_LOG(SSH_LOG_DEBUG, ++ "SSH server banner: %s", session->serverbanner); ++ ++ /* Here we analyze the different protocols the server allows. */ ++ rc = ssh_analyze_banner(session, 0); ++ if (rc < 0) { ++ ssh_set_error(session, SSH_FATAL, ++ "No version of SSH protocol usable (banner: %s)", ++ session->serverbanner); ++ goto error; ++ } ++ ++ ssh_packet_register_socket_callback(session, session->socket); + +- ssh_packet_register_socket_callback(session, session->socket); ++ ssh_packet_set_default_callbacks(session); ++ session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ rc = ssh_set_client_kex(session); ++ if (rc != SSH_OK) { ++ goto error; ++ } ++ rc = ssh_send_kex(session); ++ if (rc < 0) { ++ goto error; ++ } ++ set_status(session, 0.5f); + +- ssh_packet_set_default_callbacks(session); +- session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ break; ++ case SSH_SESSION_STATE_INITIAL_KEX: ++ /* TODO: This state should disappear in favor of get_key handle */ ++ break; ++ case SSH_SESSION_STATE_KEXINIT_RECEIVED: ++ set_status(session, 0.6f); ++ ssh_list_kex(&session->next_crypto->server_kex); ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { ++ /* in rekeying state if next_crypto client_kex might be empty */ + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } +- rc = ssh_send_kex(session, 0); ++ rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } +- set_status(session, 0.5f); ++ } ++ if (ssh_kex_select_methods(session) == SSH_ERROR) ++ goto error; ++ set_status(session, 0.8f); ++ session->session_state = SSH_SESSION_STATE_DH; + +- break; +- case SSH_SESSION_STATE_INITIAL_KEX: +- /* TODO: This state should disappear in favor of get_key handle */ +- break; +- case SSH_SESSION_STATE_KEXINIT_RECEIVED: +- set_status(session,0.6f); +- ssh_list_kex(&session->next_crypto->server_kex); +- if (session->next_crypto->client_kex.methods[0] == NULL) { +- /* in rekeying state if next_crypto client_kex is empty */ +- rc = ssh_set_client_kex(session); +- if (rc != SSH_OK) { +- goto error; +- } +- rc = ssh_send_kex(session, 0); +- if (rc < 0) { +- goto error; +- } +- } +- if (ssh_kex_select_methods(session) == SSH_ERROR) +- goto error; +- set_status(session,0.8f); +- session->session_state=SSH_SESSION_STATE_DH; +- if (dh_handshake(session) == SSH_ERROR) { +- goto error; +- } +- FALL_THROUGH; +- case SSH_SESSION_STATE_DH: +- if(session->dh_handshake_state==DH_STATE_FINISHED){ +- set_status(session,1.0f); +- session->connected = 1; +- if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) +- session->session_state = SSH_SESSION_STATE_AUTHENTICATED; +- else +- session->session_state=SSH_SESSION_STATE_AUTHENTICATING; +- } +- break; +- case SSH_SESSION_STATE_AUTHENTICATING: +- break; +- case SSH_SESSION_STATE_ERROR: ++ /* If the init packet was already sent in previous step, this will be no ++ * operation */ ++ if (dh_handshake(session) == SSH_ERROR) { + goto error; +- default: +- ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); ++ } ++ FALL_THROUGH; ++ case SSH_SESSION_STATE_DH: ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { ++ set_status(session, 1.0f); ++ session->connected = 1; ++ if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATED; ++ else ++ session->session_state=SSH_SESSION_STATE_AUTHENTICATING; ++ } ++ break; ++ case SSH_SESSION_STATE_AUTHENTICATING: ++ break; ++ case SSH_SESSION_STATE_ERROR: ++ goto error; ++ default: ++ ssh_set_error(session, SSH_FATAL, "Invalid state %d", ++ session->session_state); + } + + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; +- session->session_state=SSH_SESSION_STATE_ERROR; +- SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); ++ session->session_state = SSH_SESSION_STATE_ERROR; ++ + } + + /** @internal +diff --color -ru ../libssh-0.9.6/src/curve25519.c ./src/curve25519.c +--- ../libssh-0.9.6/src/curve25519.c 2023-04-27 12:22:39.797925403 +0200 ++++ ./src/curve25519.c 2023-04-27 12:24:04.680475778 +0200 +@@ -172,6 +172,11 @@ + return rc; + } + ++void ssh_client_curve25519_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); ++} ++ + static int ssh_curve25519_build_k(ssh_session session) + { + ssh_curve25519_pubkey k; +@@ -285,7 +290,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); ++ ssh_client_curve25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { +diff --color -ru ../libssh-0.9.6/src/dh.c ./src/dh.c +--- ../libssh-0.9.6/src/dh.c 2023-04-27 12:22:39.798925404 +0200 ++++ ./src/dh.c 2023-04-27 12:24:04.680475778 +0200 +@@ -342,6 +342,11 @@ + return SSH_ERROR; + } + ++void ssh_client_dh_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); ++} ++ + SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL; +@@ -351,7 +356,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); ++ ssh_client_dh_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); +diff --color -ru ../libssh-0.9.6/src/dh-gex.c ./src/dh-gex.c +--- ../libssh-0.9.6/src/dh-gex.c 2023-04-27 12:22:39.797925403 +0200 ++++ ./src/dh-gex.c 2023-04-27 12:24:04.680475778 +0200 +@@ -238,6 +238,11 @@ + return SSH_PACKET_USED; + } + ++void ssh_client_dhgex_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); ++} ++ + static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) + { + struct ssh_crypto_struct *crypto=session->next_crypto; +@@ -248,7 +253,7 @@ + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); + +- ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); ++ ssh_client_dhgex_remove_callbacks(session); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &server_pubkey, +diff --color -ru ../libssh-0.9.6/src/ecdh.c ./src/ecdh.c +--- ../libssh-0.9.6/src/ecdh.c 2023-04-27 12:22:39.798925404 +0200 ++++ ./src/ecdh.c 2023-04-27 12:24:04.680475778 +0200 +@@ -43,6 +43,11 @@ + .user = NULL + }; + ++void ssh_client_ecdh_remove_callbacks(ssh_session session) ++{ ++ ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); ++} ++ + /** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS +@@ -55,7 +60,7 @@ + (void)type; + (void)user; + +- ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); ++ ssh_client_ecdh_remove_callbacks(session); + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); +diff --color -ru ../libssh-0.9.6/src/gssapi.c ./src/gssapi.c +--- ../libssh-0.9.6/src/gssapi.c 2023-04-27 12:22:39.798925404 +0200 ++++ ./src/gssapi.c 2023-04-27 12:24:04.681475778 +0200 +@@ -223,6 +223,7 @@ + "indicate mechs", + maj_stat, + min_stat); ++ gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + +@@ -259,8 +260,10 @@ + return SSH_OK; + } + /* from now we have room for context */ +- if (ssh_gssapi_init(session) == SSH_ERROR) ++ if (ssh_gssapi_init(session) == SSH_ERROR) { ++ gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; ++ } + + name_buf.value = service_name; + name_buf.length = strlen(name_buf.value) + 1; +@@ -272,6 +275,7 @@ + "importing name", + maj_stat, + min_stat); ++ gss_release_oid_set(&min_stat, &both_supported); + return -1; + } + +@@ -338,6 +342,7 @@ + min_stat); + ptr = malloc(buffer.length + 1); + if (ptr == NULL) { ++ gss_release_buffer(&min_stat, &buffer); + return NULL; + } + memcpy(ptr, buffer.value, buffer.length); +@@ -421,6 +426,7 @@ + "Gssapi error", + maj_stat, + min_stat); ++ gss_release_buffer(&min_stat, &output_token); + ssh_auth_reply_default(session,0); + ssh_gssapi_free(session); + session->gssapi=NULL; +@@ -438,6 +444,9 @@ + (size_t)output_token.length, output_token.value); + ssh_packet_send(session); + } ++ ++ gss_release_buffer(&min_stat, &output_token); ++ + if(maj_stat == GSS_S_COMPLETE){ + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } +@@ -638,7 +647,7 @@ + static int ssh_gssapi_match(ssh_session session, gss_OID_set *valid_oids) + { + OM_uint32 maj_stat, min_stat, lifetime; +- gss_OID_set actual_mechs; ++ gss_OID_set actual_mechs = GSS_C_NO_OID_SET; + gss_buffer_desc namebuf; + gss_name_t client_id = GSS_C_NO_NAME; + gss_OID oid; +@@ -700,6 +709,7 @@ + ret = SSH_OK; + + end: ++ gss_release_oid_set(&min_stat, &actual_mechs); + gss_release_name(&min_stat, &client_id); + return ret; + } +@@ -713,7 +723,7 @@ + */ + int ssh_gssapi_auth_mic(ssh_session session){ + size_t i; +- gss_OID_set selected; /* oid selected for authentication */ ++ gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; +@@ -790,6 +800,8 @@ + SSH_STRING_FREE(oids[i]); + } + free(oids); ++ gss_release_oid_set(&min_stat, &selected); ++ + if (rc != SSH_ERROR) { + return SSH_AUTH_AGAIN; + } +@@ -893,6 +905,8 @@ + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } ++ ++ gss_release_buffer(&min_stat, &output_token); + return SSH_PACKET_USED; + + error: +@@ -921,8 +935,10 @@ + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); ++ ++ SSH_BUFFER_FREE(mic_buffer); ++ + if (GSS_ERROR(maj_stat)){ +- SSH_BUFFER_FREE(mic_buffer); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, +@@ -935,8 +951,10 @@ + SSH2_MSG_USERAUTH_GSSAPI_MIC, + mic_token_buf.length, + (size_t)mic_token_buf.length, mic_token_buf.value); ++ ++ gss_release_buffer(&min_stat, &mic_token_buf); ++ + if (rc != SSH_OK) { +- SSH_BUFFER_FREE(mic_buffer); + ssh_set_error_oom(session); + return SSH_ERROR; + } +@@ -1005,6 +1023,8 @@ + ssh_packet_send(session); + } + ++ gss_release_buffer(&min_stat, &output_token); ++ + if (maj_stat == GSS_S_COMPLETE) { + ssh_gssapi_send_mic(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; +diff --color -ru ../libssh-0.9.6/src/kex.c ./src/kex.c +--- ../libssh-0.9.6/src/kex.c 2023-04-27 12:22:39.798925404 +0200 ++++ ./src/kex.c 2023-04-27 12:24:08.450478574 +0200 +@@ -28,6 +28,7 @@ + #include + #include + ++#include "libssh/libssh.h" + #include "libssh/priv.h" + #include "libssh/buffer.h" + #include "libssh/dh.h" +@@ -305,6 +306,10 @@ + + int is_wrong = 1; + ++ if (client_str == NULL || server_str == NULL) { ++ return is_wrong; ++ } ++ + colon = strchr(client_str, ','); + if (colon == NULL) { + client_kex_len = strlen(client_str); +@@ -331,6 +336,7 @@ + SSH_PACKET_CALLBACK(ssh_packet_kexinit) + { + int i, ok; ++ struct ssh_crypto_struct *crypto = session->next_crypto; + int server_kex = session->server; + ssh_string str = NULL; + char *strings[SSH_KEX_METHODS] = {0}; +@@ -344,35 +350,67 @@ + (void)type; + (void)user; + ++ SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); ++ + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { +- SSH_LOG(SSH_LOG_DEBUG, "Initiating key re-exchange"); ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { ++ SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); ++ /* Reset the sent flag if the re-kex was initiated by the peer */ ++ session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; ++ } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && ++ session->dh_handshake_state == DH_STATE_INIT_SENT) { ++ /* This happens only when we are sending our-guessed first kex ++ * packet right after our KEXINIT packet. */ ++ SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); ++ } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { ++ ssh_set_error(session, SSH_FATAL, ++ "SSH_KEXINIT received in wrong state"); ++ goto error; ++ } + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { +- ssh_set_error(session,SSH_FATAL,"SSH_KEXINIT received in wrong state"); ++ ssh_set_error(session, SSH_FATAL, ++ "SSH_KEXINIT received in wrong state"); + goto error; + } + + if (server_kex) { +- len = ssh_buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); ++#ifdef WITH_SERVER ++ len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); + if (len != 16) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + +- ok = ssh_hashbufin_add_cookie(session, session->next_crypto->client_kex.cookie); ++ ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); + if (ok < 0) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: adding cookie failed"); ++ goto error; ++ } ++ ++ ok = server_set_kex(session); ++ if (ok == SSH_ERROR) { + goto error; + } ++#endif + } else { +- len = ssh_buffer_get_data(packet,session->next_crypto->server_kex.cookie, 16); ++ len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); + if (len != 16) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + +- ok = ssh_hashbufin_add_cookie(session, session->next_crypto->server_kex.cookie); ++ ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); + if (ok < 0) { +- ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: adding cookie failed"); ++ ssh_set_error(session, SSH_FATAL, ++ "ssh_packet_kexinit: adding cookie failed"); ++ goto error; ++ } ++ ++ ok = ssh_set_client_kex(session); ++ if (ok == SSH_ERROR) { + goto error; + } + } +@@ -385,7 +423,8 @@ + + rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); + if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, "Error adding string in hash buffer"); ++ ssh_set_error(session, SSH_FATAL, ++ "Error adding string in hash buffer"); + goto error; + } + +@@ -398,14 +437,14 @@ + str = NULL; + } + +- /* copy the server kex info into an array of strings */ ++ /* copy the peer kex info into an array of strings */ + if (server_kex) { + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->client_kex.methods[i] = strings[i]; ++ crypto->client_kex.methods[i] = strings[i]; + } + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->server_kex.methods[i] = strings[i]; ++ crypto->server_kex.methods[i] = strings[i]; + } + } + +@@ -419,30 +458,48 @@ + * that its value is included when computing the session ID (see + * 'make_sessionid'). + */ +- if (server_kex) { +- rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); +- if (rc != 1) { +- goto error; +- } ++ rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); ++ if (rc != 1) { ++ goto error; ++ } + +- rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); +- if (rc < 0) { +- goto error; +- } ++ rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); ++ if (rc < 0) { ++ goto error; ++ } + +- rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); +- if (rc < 0) { +- goto error; +- } ++ rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); ++ if (rc < 0) { ++ goto error; ++ } ++ ++ /* ++ * Remember whether 'first_kex_packet_follows' was set and the client ++ * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message ++ * must be ignored on the server side. ++ * Client needs to start the Key exchange over with the correct method ++ */ ++ if (first_kex_packet_follows || session->send_first_kex_follows) { ++ char **client_methods = crypto->client_kex.methods; ++ char **server_methods = crypto->server_kex.methods; ++ session->first_kex_follows_guess_wrong = ++ cmp_first_kex_algo(client_methods[SSH_KEX], ++ server_methods[SSH_KEX]) || ++ cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], ++ server_methods[SSH_HOSTKEYS]); ++ SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", ++ session->first_kex_follows_guess_wrong ? "wrong" : "right"); ++ } + ++ if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports + * RFC 8308 extension negotiation. + */ +- ok = ssh_match_group(session->next_crypto->client_kex.methods[SSH_KEX], ++ ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], + KEX_EXTENSION_CLIENT); + if (ok) { +- const char *hostkeys = NULL; ++ const char *hostkeys = NULL, *wanted_hostkeys = NULL; + + /* The client supports extension negotiation */ + session->extensions |= SSH_EXT_NEGOTIATION; +@@ -452,14 +509,14 @@ + * by the client and enable the respective extensions to provide + * correct signature in the next packet if RSA is negotiated + */ +- hostkeys = session->next_crypto->client_kex.methods[SSH_HOSTKEYS]; ++ hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; ++ wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; + ok = ssh_match_group(hostkeys, "rsa-sha2-512"); + if (ok) { + /* Check if rsa-sha2-512 is allowed by config */ +- if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { +- char *is_allowed = +- ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], +- "rsa-sha2-512"); ++ if (wanted_hostkeys != NULL) { ++ char *is_allowed = ssh_find_matching(wanted_hostkeys, ++ "rsa-sha2-512"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } +@@ -469,10 +526,9 @@ + ok = ssh_match_group(hostkeys, "rsa-sha2-256"); + if (ok) { + /* Check if rsa-sha2-256 is allowed by config */ +- if (session->opts.wanted_methods[SSH_HOSTKEYS] != NULL) { +- char *is_allowed = +- ssh_find_matching(session->opts.wanted_methods[SSH_HOSTKEYS], +- "rsa-sha2-256"); ++ if (wanted_hostkeys != NULL) { ++ char *is_allowed = ssh_find_matching(wanted_hostkeys, ++ "rsa-sha2-256"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } +@@ -488,7 +544,7 @@ + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); + rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", +- session->next_crypto->client_kex.methods[SSH_HOSTKEYS]); ++ hostkeys); + if (rsa_sig_ext == NULL) { + goto error; /* should never happen */ + } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { +@@ -507,24 +563,16 @@ + session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", + session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); + } +- +- /* +- * Remember whether 'first_kex_packet_follows' was set and the client +- * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message +- * must be ignored. +- */ +- if (first_kex_packet_follows) { +- session->first_kex_follows_guess_wrong = +- cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_KEX], +- session->next_crypto->server_kex.methods[SSH_KEX]) || +- cmp_first_kex_algo(session->next_crypto->client_kex.methods[SSH_HOSTKEYS], +- session->next_crypto->server_kex.methods[SSH_HOSTKEYS]); +- } + } + + /* Note, that his overwrites authenticated state in case of rekeying */ + session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; +- session->dh_handshake_state = DH_STATE_INIT; ++ /* if we already sent our initial key exchange packet, do not reset the ++ * DH state. We will know if we were right with our guess only in ++ * dh_handshake_state() */ ++ if (session->send_first_kex_follows == false) { ++ session->dh_handshake_state = DH_STATE_INIT; ++ } + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +@@ -672,14 +720,18 @@ + int i; + size_t kex_len, len; + ++ /* Skip if already set, for example for the rekey or when we do the guessing ++ * it could have been already used to make some protocol decisions. */ ++ if (client->methods[0] != NULL) { ++ return SSH_OK; ++ } ++ + ok = ssh_get_random(client->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + +- memset(client->methods, 0, SSH_KEX_METHODS * sizeof(char **)); +- + /* Set the list of allowed algorithms in order of preference, if it hadn't + * been set yet. */ + for (i = 0; i < SSH_KEX_METHODS; i++) { +@@ -749,15 +801,89 @@ + return NULL; + } + ++static enum ssh_key_exchange_e ++kex_select_kex_type(const char *kex) ++{ ++ if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { ++ return SSH_KEX_DH_GROUP1_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { ++ return SSH_KEX_DH_GROUP14_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { ++ return SSH_KEX_DH_GROUP14_SHA256; ++ } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { ++ return SSH_KEX_DH_GROUP16_SHA512; ++ } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { ++ return SSH_KEX_DH_GROUP18_SHA512; ++#ifdef WITH_GEX ++ } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { ++ return SSH_KEX_DH_GEX_SHA1; ++ } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { ++ return SSH_KEX_DH_GEX_SHA256; ++#endif /* WITH_GEX */ ++ } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP256; ++ } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP384; ++ } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { ++ return SSH_KEX_ECDH_SHA2_NISTP521; ++ } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { ++ return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; ++ } else if (strcmp(kex, "curve25519-sha256") == 0) { ++ return SSH_KEX_CURVE25519_SHA256; ++ } ++ /* should not happen. We should be getting only valid names at this stage */ ++ return 0; ++} ++ ++ ++/** @internal ++ * @brief Reverts guessed callbacks set during the dh_handshake() ++ * @param session session handle ++ * @returns void ++ */ ++static void revert_kex_callbacks(ssh_session session) ++{ ++ switch (session->next_crypto->kex_type) { ++ case SSH_KEX_DH_GROUP1_SHA1: ++ case SSH_KEX_DH_GROUP14_SHA1: ++ case SSH_KEX_DH_GROUP14_SHA256: ++ case SSH_KEX_DH_GROUP16_SHA512: ++ case SSH_KEX_DH_GROUP18_SHA512: ++ ssh_client_dh_remove_callbacks(session); ++ break; ++#ifdef WITH_GEX ++ case SSH_KEX_DH_GEX_SHA1: ++ case SSH_KEX_DH_GEX_SHA256: ++ ssh_client_dhgex_remove_callbacks(session); ++ break; ++#endif /* WITH_GEX */ ++#ifdef HAVE_ECDH ++ case SSH_KEX_ECDH_SHA2_NISTP256: ++ case SSH_KEX_ECDH_SHA2_NISTP384: ++ case SSH_KEX_ECDH_SHA2_NISTP521: ++ ssh_client_ecdh_remove_callbacks(session); ++ break; ++#endif ++#ifdef HAVE_CURVE25519 ++ case SSH_KEX_CURVE25519_SHA256: ++ case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: ++ ssh_client_curve25519_remove_callbacks(session); ++ break; ++#endif ++ } ++} ++ + /** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ + int ssh_kex_select_methods (ssh_session session) + { +- struct ssh_kex_struct *server = &session->next_crypto->server_kex; +- struct ssh_kex_struct *client = &session->next_crypto->client_kex; ++ struct ssh_crypto_struct *crypto = session->next_crypto; ++ struct ssh_kex_struct *server = &crypto->server_kex; ++ struct ssh_kex_struct *client = &crypto->client_kex; + char *ext_start = NULL; + const char *aead_hmac = NULL; ++ enum ssh_key_exchange_e kex_type; + int i; + + /* Here we should drop the ext-info-c from the list so we avoid matching. +@@ -768,52 +894,41 @@ + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { +- session->next_crypto->kex_methods[i]=ssh_find_matching(server->methods[i],client->methods[i]); ++ crypto->kex_methods[i] = ssh_find_matching(server->methods[i], ++ client->methods[i]); + + if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { +- aead_hmac = ssh_find_aead_hmac(session->next_crypto->kex_methods[i-2]); ++ aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); + if (aead_hmac) { +- free(session->next_crypto->kex_methods[i]); +- session->next_crypto->kex_methods[i] = strdup(aead_hmac); ++ free(crypto->kex_methods[i]); ++ crypto->kex_methods[i] = strdup(aead_hmac); + } + } +- if (session->next_crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S){ +- ssh_set_error(session,SSH_FATAL,"kex error : no match for method %s: server [%s], client [%s]", +- ssh_kex_descriptions[i],server->methods[i],client->methods[i]); ++ if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { ++ ssh_set_error(session, SSH_FATAL, ++ "kex error : no match for method %s: server [%s], " ++ "client [%s]", ssh_kex_descriptions[i], ++ server->methods[i], client->methods[i]); + return SSH_ERROR; +- } else if ((i >= SSH_LANG_C_S) && (session->next_crypto->kex_methods[i] == NULL)) { ++ } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ +- session->next_crypto->kex_methods[i] = strdup(""); ++ crypto->kex_methods[i] = strdup(""); + } + } +- if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group1-sha1") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP1_SHA1; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha1") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA1; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group14-sha256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP14_SHA256; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group16-sha512") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP16_SHA512; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group18-sha512") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GROUP18_SHA512; +-#ifdef WITH_GEX +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha1") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA1; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "diffie-hellman-group-exchange-sha256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_DH_GEX_SHA256; +-#endif /* WITH_GEX */ +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP256; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp384") == 0){ +- session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP384; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "ecdh-sha2-nistp521") == 0){ +- session->next_crypto->kex_type=SSH_KEX_ECDH_SHA2_NISTP521; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256@libssh.org") == 0){ +- session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; +- } else if (strcmp(session->next_crypto->kex_methods[SSH_KEX], "curve25519-sha256") == 0){ +- session->next_crypto->kex_type=SSH_KEX_CURVE25519_SHA256; ++ ++ /* We can not set this value directly as the old value is needed to revert ++ * callbacks if we are client */ ++ kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); ++ if (session->client && session->first_kex_follows_guess_wrong) { ++ SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); ++ /* We need to remove the wrong callbacks and start kex again */ ++ revert_kex_callbacks(session); ++ session->dh_handshake_state = DH_STATE_INIT; ++ session->first_kex_follows_guess_wrong = false; + } +- SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", ++ crypto->kex_type = kex_type; ++ ++ SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + session->next_crypto->kex_methods[SSH_KEX], + session->next_crypto->kex_methods[SSH_HOSTKEYS], + session->next_crypto->kex_methods[SSH_CRYPT_C_S], +@@ -830,63 +945,116 @@ + + + /* this function only sends the predefined set of kex methods */ +-int ssh_send_kex(ssh_session session, int server_kex) ++int ssh_send_kex(ssh_session session) + { +- struct ssh_kex_struct *kex = (server_kex ? &session->next_crypto->server_kex : +- &session->next_crypto->client_kex); +- ssh_string str = NULL; +- int i; +- int rc; +- +- rc = ssh_buffer_pack(session->out_buffer, +- "bP", +- SSH2_MSG_KEXINIT, +- 16, +- kex->cookie); /* cookie */ +- if (rc != SSH_OK) +- goto error; +- if (ssh_hashbufout_add_cookie(session) < 0) { +- goto error; +- } ++ struct ssh_kex_struct *kex = (session->server ? ++ &session->next_crypto->server_kex : ++ &session->next_crypto->client_kex); ++ ssh_string str = NULL; ++ int i; ++ int rc; ++ int first_kex_packet_follows = 0; ++ ++ /* Only client can initiate the handshake methods we implement. If we ++ * already received the peer mechanisms, there is no point in guessing */ ++ if (session->client && ++ session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && ++ session->send_first_kex_follows) { ++ first_kex_packet_follows = 1; ++ } ++ ++ SSH_LOG(SSH_LOG_TRACE, ++ "Sending KEXINIT packet, first_kex_packet_follows = %d", ++ first_kex_packet_follows); ++ ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bP", ++ SSH2_MSG_KEXINIT, ++ 16, ++ kex->cookie); /* cookie */ ++ if (rc != SSH_OK) ++ goto error; ++ if (ssh_hashbufout_add_cookie(session) < 0) { ++ goto error; ++ } ++ ++ ssh_list_kex(kex); + +- ssh_list_kex(kex); ++ for (i = 0; i < SSH_KEX_METHODS; i++) { ++ str = ssh_string_from_char(kex->methods[i]); ++ if (str == NULL) { ++ goto error; ++ } ++ ++ rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); ++ if (rc < 0) { ++ goto error; ++ } ++ rc = ssh_buffer_add_ssh_string(session->out_buffer, str); ++ if (rc < 0) { ++ goto error; ++ } ++ SSH_STRING_FREE(str); ++ str = NULL; ++ } + +- for (i = 0; i < SSH_KEX_METHODS; i++) { +- str = ssh_string_from_char(kex->methods[i]); +- if (str == NULL) { +- goto error; ++ rc = ssh_buffer_pack(session->out_buffer, ++ "bd", ++ first_kex_packet_follows, ++ 0); ++ if (rc != SSH_OK) { ++ goto error; + } + +- if (ssh_buffer_add_ssh_string(session->out_hashbuf, str) < 0) { +- goto error; ++ /* Prepare also the first_kex_packet_follows and reserved to 0 */ ++ rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); ++ if (rc < 0) { ++ goto error; + } +- if (ssh_buffer_add_ssh_string(session->out_buffer, str) < 0) { +- goto error; ++ rc = ssh_buffer_add_u32(session->out_hashbuf, 0); ++ if (rc < 0) { ++ goto error; + } +- SSH_STRING_FREE(str); +- str = NULL; +- } + +- rc = ssh_buffer_pack(session->out_buffer, +- "bd", +- 0, +- 0); +- if (rc != SSH_OK) { +- goto error; +- } ++ rc = ssh_packet_send(session); ++ if (rc == SSH_ERROR) { ++ return -1; ++ } + +- if (ssh_packet_send(session) == SSH_ERROR) { +- return -1; +- } ++ session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; ++ SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); ++ ++ /* If we indicated that we are sending the guessed key exchange packet, ++ * do it now. The packet is simple, but we need to do some preparations */ ++ if (first_kex_packet_follows) { ++ char *list = kex->methods[SSH_KEX]; ++ char *colon = strchr(list, ','); ++ size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); ++ char *kex_name = calloc(kex_name_len + 1, 1); ++ if (kex_name == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); ++ SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); ++ ++ session->next_crypto->kex_type = kex_select_kex_type(kex_name); ++ free(kex_name); ++ ++ /* run the first step of the DH handshake */ ++ session->dh_handshake_state = DH_STATE_INIT; ++ if (dh_handshake(session) == SSH_ERROR) { ++ goto error; ++ } ++ } ++ return 0; + +- SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); +- return 0; + error: +- ssh_buffer_reinit(session->out_buffer); +- ssh_buffer_reinit(session->out_hashbuf); +- SSH_STRING_FREE(str); ++ ssh_buffer_reinit(session->out_buffer); ++ ssh_buffer_reinit(session->out_hashbuf); ++ SSH_STRING_FREE(str); + +- return -1; ++ return -1; + } + + /* +@@ -929,7 +1097,7 @@ + } + + session->dh_handshake_state = DH_STATE_INIT; +- rc = ssh_send_kex(session, session->server); ++ rc = ssh_send_kex(session); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); + return rc; +@@ -1006,33 +1174,6 @@ + client_hash = session->in_hashbuf; + } + +- /* +- * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): +- * +- * boolean first_kex_packet_follows +- * uint32 0 (reserved for future extension) +- */ +- rc = ssh_buffer_add_u8(server_hash, 0); +- if (rc < 0) { +- goto error; +- } +- rc = ssh_buffer_add_u32(server_hash, 0); +- if (rc < 0) { +- goto error; +- } +- +- /* These fields are handled for the server case in ssh_packet_kexinit. */ +- if (session->client) { +- rc = ssh_buffer_add_u8(client_hash, 0); +- if (rc < 0) { +- goto error; +- } +- rc = ssh_buffer_add_u32(client_hash, 0); +- if (rc < 0) { +- goto error; +- } +- } +- + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; +diff --color -ru ../libssh-0.9.6/src/packet.c ./src/packet.c +--- ../libssh-0.9.6/src/packet.c 2023-04-27 12:22:39.811925413 +0200 ++++ ./src/packet.c 2023-04-27 12:24:04.682475779 +0200 +@@ -366,6 +366,11 @@ + * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT + * */ + ++ if (!session->server) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; +@@ -1410,18 +1415,23 @@ + } + } + +-void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s){ +- session->socket_callbacks.data=ssh_packet_socket_callback; +- session->socket_callbacks.connected=NULL; +- session->socket_callbacks.controlflow = ssh_packet_socket_controlflow_callback; +- session->socket_callbacks.userdata=session; +- ssh_socket_set_callbacks(s,&session->socket_callbacks); ++void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) ++{ ++ struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; ++ ++ callbacks->data = ssh_packet_socket_callback; ++ callbacks->connected = NULL; ++ callbacks->controlflow = ssh_packet_socket_controlflow_callback; ++ callbacks->userdata = session; ++ ssh_socket_set_callbacks(s, callbacks); + } + + /** @internal + * @brief sets the callbacks for the packet layer + */ +-void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ ++void ++ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks) ++{ + if (session->packet_callbacks == NULL) { + session->packet_callbacks = ssh_list_new(); + if (session->packet_callbacks == NULL) { +@@ -1435,8 +1445,11 @@ + /** @internal + * @brief remove the callbacks from the packet layer + */ +-void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks){ ++void ++ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) ++{ + struct ssh_iterator *it = NULL; ++ + it = ssh_list_find(session->packet_callbacks, callbacks); + if (it != NULL) { + ssh_list_remove(session->packet_callbacks, it); +@@ -1446,12 +1459,15 @@ + /** @internal + * @brief sets the default packet handlers + */ +-void ssh_packet_set_default_callbacks(ssh_session session){ +- session->default_packet_callbacks.start=1; +- session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers)/sizeof(ssh_packet_callback); +- session->default_packet_callbacks.user=session; +- session->default_packet_callbacks.callbacks=default_packet_handlers; +- ssh_packet_set_callbacks(session, &session->default_packet_callbacks); ++void ssh_packet_set_default_callbacks(ssh_session session) ++{ ++ struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; ++ ++ c->start = 1; ++ c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); ++ c->user = session; ++ c->callbacks = default_packet_handlers; ++ ssh_packet_set_callbacks(session, c); + } + + /** @internal +@@ -1928,7 +1944,7 @@ + memcpy(session->next_crypto->session_id, + session->current_crypto->session_id, + session_id_len); +- session->next_crypto->session_id_len = session_id_len; ++ session->next_crypto->session_id_len = session_id_len; + + return SSH_OK; + } +diff --color -ru ../libssh-0.9.6/src/packet_cb.c ./src/packet_cb.c +--- ../libssh-0.9.6/src/packet_cb.c 2023-04-27 12:22:39.799925404 +0200 ++++ ./src/packet_cb.c 2023-04-27 12:24:04.682475779 +0200 +@@ -156,6 +156,9 @@ + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Failed to verify server hostkey signature"); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG,"Signature verified and valid"); +diff --color -ru ../libssh-0.9.6/src/server.c ./src/server.c +--- ../libssh-0.9.6/src/server.c 2023-04-27 12:22:39.800925405 +0200 ++++ ./src/server.c 2023-04-27 12:24:04.683475780 +0200 +@@ -92,7 +92,11 @@ + size_t len; + int ok; + +- ZERO_STRUCTP(server); ++ /* Skip if already set, for example for the rekey or when we do the guessing ++ * it could have been already used to make some protocol decisions. */ ++ if (server->methods[0] != NULL) { ++ return SSH_OK; ++ } + + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { +@@ -335,117 +339,121 @@ + * @brief A function to be called each time a step has been done in the + * connection. + */ +-static void ssh_server_connection_callback(ssh_session session){ ++static void ssh_server_connection_callback(ssh_session session) ++{ + int rc; + +- switch(session->session_state){ +- case SSH_SESSION_STATE_NONE: +- case SSH_SESSION_STATE_CONNECTING: +- case SSH_SESSION_STATE_SOCKET_CONNECTED: +- break; +- case SSH_SESSION_STATE_BANNER_RECEIVED: +- if (session->clientbanner == NULL) { ++ switch (session->session_state) { ++ case SSH_SESSION_STATE_NONE: ++ case SSH_SESSION_STATE_CONNECTING: ++ case SSH_SESSION_STATE_SOCKET_CONNECTED: ++ break; ++ case SSH_SESSION_STATE_BANNER_RECEIVED: ++ if (session->clientbanner == NULL) { ++ goto error; ++ } ++ set_status(session, 0.4f); ++ SSH_LOG(SSH_LOG_DEBUG, ++ "SSH client banner: %s", session->clientbanner); ++ ++ /* Here we analyze the different protocols the server allows. */ ++ rc = ssh_analyze_banner(session, 1); ++ if (rc < 0) { ++ ssh_set_error(session, SSH_FATAL, ++ "No version of SSH protocol usable (banner: %s)", ++ session->clientbanner); ++ goto error; ++ } ++ ++ /* from now, the packet layer is handling incoming packets */ ++ session->socket_callbacks.data = ssh_packet_socket_callback; ++ ssh_packet_register_socket_callback(session, session->socket); ++ ++ ssh_packet_set_default_callbacks(session); ++ set_status(session, 0.5f); ++ session->session_state = SSH_SESSION_STATE_INITIAL_KEX; ++ if (ssh_send_kex(session) < 0) { ++ goto error; ++ } ++ break; ++ case SSH_SESSION_STATE_INITIAL_KEX: ++ /* TODO: This state should disappear in favor of get_key handle */ ++ break; ++ case SSH_SESSION_STATE_KEXINIT_RECEIVED: ++ set_status(session, 0.6f); ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { ++ if (server_set_kex(session) == SSH_ERROR) + goto error; +- } +- set_status(session, 0.4f); +- SSH_LOG(SSH_LOG_DEBUG, +- "SSH client banner: %s", session->clientbanner); +- +- /* Here we analyze the different protocols the server allows. */ +- rc = ssh_analyze_banner(session, 1); +- if (rc < 0) { +- ssh_set_error(session, SSH_FATAL, +- "No version of SSH protocol usable (banner: %s)", +- session->clientbanner); ++ /* We are in a rekeying, so we need to send the server kex */ ++ if (ssh_send_kex(session) < 0) + goto error; +- } ++ } ++ ssh_list_kex(&session->next_crypto->client_kex); // log client kex ++ if (ssh_kex_select_methods(session) < 0) { ++ goto error; ++ } ++ if (crypt_set_algorithms_server(session) == SSH_ERROR) ++ goto error; ++ set_status(session, 0.8f); ++ session->session_state = SSH_SESSION_STATE_DH; ++ break; ++ case SSH_SESSION_STATE_DH: ++ if (session->dh_handshake_state == DH_STATE_FINISHED) { + +- /* from now, the packet layer is handling incoming packets */ +- session->socket_callbacks.data=ssh_packet_socket_callback; +- ssh_packet_register_socket_callback(session, session->socket); +- +- ssh_packet_set_default_callbacks(session); +- set_status(session, 0.5f); +- session->session_state=SSH_SESSION_STATE_INITIAL_KEX; +- if (ssh_send_kex(session, 1) < 0) { +- goto error; +- } +- break; +- case SSH_SESSION_STATE_INITIAL_KEX: +- /* TODO: This state should disappear in favor of get_key handle */ +- break; +- case SSH_SESSION_STATE_KEXINIT_RECEIVED: +- set_status(session,0.6f); +- if(session->next_crypto->server_kex.methods[0]==NULL){ +- if(server_set_kex(session) == SSH_ERROR) +- goto error; +- /* We are in a rekeying, so we need to send the server kex */ +- if(ssh_send_kex(session, 1) < 0) +- goto error; +- } +- ssh_list_kex(&session->next_crypto->client_kex); // log client kex +- if (ssh_kex_select_methods(session) < 0) { ++ rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); ++ if (rc != SSH_OK) { + goto error; + } +- if (crypt_set_algorithms_server(session) == SSH_ERROR) +- goto error; +- set_status(session,0.8f); +- session->session_state=SSH_SESSION_STATE_DH; +- break; +- case SSH_SESSION_STATE_DH: +- if(session->dh_handshake_state==DH_STATE_FINISHED){ +- +- rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); +- if (rc != SSH_OK) { +- goto error; +- } ++ ++ /* ++ * If the client supports extension negotiation, we will send ++ * our supported extensions now. This is the first message after ++ * sending NEWKEYS message and after turning on crypto. ++ */ ++ if (session->extensions & SSH_EXT_NEGOTIATION && ++ session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + + /* +- * If the client supports extension negotiation, we will send +- * our supported extensions now. This is the first message after +- * sending NEWKEYS message and after turning on crypto. ++ * Only send an SSH_MSG_EXT_INFO message the first time the ++ * client undergoes NEWKEYS. It is unexpected for this message ++ * to be sent upon rekey, and may cause clients to log error ++ * messages. ++ * ++ * The session_state can not be used for this purpose because it ++ * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. ++ * So, use the connected flag which transitions from non-zero ++ * below. ++ * ++ * See also: ++ * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 + */ +- if (session->extensions & SSH_EXT_NEGOTIATION && +- session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { +- +- /* +- * Only send an SSH_MSG_EXT_INFO message the first time the client +- * undergoes NEWKEYS. It is unexpected for this message to be sent +- * upon rekey, and may cause clients to log error messages. +- * +- * The session_state can not be used for this purpose because it is +- * re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. So, +- * use the connected flag which transitions from non-zero below. +- * +- * See also: +- * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 +- */ +- if (session->connected == 0) { +- ssh_server_send_extensions(session); +- } ++ if (session->connected == 0) { ++ ssh_server_send_extensions(session); + } ++ } + +- set_status(session,1.0f); +- session->connected = 1; +- session->session_state=SSH_SESSION_STATE_AUTHENTICATING; +- if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) +- session->session_state = SSH_SESSION_STATE_AUTHENTICATED; ++ set_status(session, 1.0f); ++ session->connected = 1; ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATING; ++ if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) ++ session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + +- } +- break; +- case SSH_SESSION_STATE_AUTHENTICATING: +- break; +- case SSH_SESSION_STATE_ERROR: +- goto error; +- default: +- ssh_set_error(session,SSH_FATAL,"Invalid state %d",session->session_state); ++ } ++ break; ++ case SSH_SESSION_STATE_AUTHENTICATING: ++ break; ++ case SSH_SESSION_STATE_ERROR: ++ goto error; ++ default: ++ ssh_set_error(session, SSH_FATAL, "Invalid state %d", ++ session->session_state); + } + + return; + error: + ssh_socket_close(session->socket); + session->alive = 0; +- session->session_state=SSH_SESSION_STATE_ERROR; ++ session->session_state = SSH_SESSION_STATE_ERROR; + } + + /** +@@ -459,16 +467,17 @@ + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +-static int callback_receive_banner(const void *data, size_t len, void *user) { +- char *buffer = (char *) data; +- ssh_session session = (ssh_session) user; ++static int callback_receive_banner(const void *data, size_t len, void *user) ++{ ++ char *buffer = (char *)data; ++ ssh_session session = (ssh_session)user; + char *str = NULL; + size_t i; + int ret=0; + + for (i = 0; i < len; i++) { + #ifdef WITH_PCAP +- if(session->pcap_ctx && buffer[i] == '\n') { ++ if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, +@@ -477,11 +486,11 @@ + } + #endif + if (buffer[i] == '\r') { +- buffer[i]='\0'; ++ buffer[i] = '\0'; + } + + if (buffer[i] == '\n') { +- buffer[i]='\0'; ++ buffer[i] = '\0'; + + str = strdup(buffer); + /* number of bytes read */ +@@ -494,10 +503,11 @@ + return ret; + } + +- if(i > 127) { ++ if (i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; +- ssh_set_error(session, SSH_FATAL, "Receiving banner: too large banner"); ++ ssh_set_error(session, SSH_FATAL, ++ "Receiving banner: too large banner"); + + return 0; + } +@@ -525,10 +535,14 @@ + } + + /* Do the banner and key exchange */ +-int ssh_handle_key_exchange(ssh_session session) { ++int ssh_handle_key_exchange(ssh_session session) ++{ + int rc; +- if (session->session_state != SSH_SESSION_STATE_NONE) +- goto pending; ++ ++ if (session->session_state != SSH_SESSION_STATE_NONE) { ++ goto pending; ++ } ++ + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; +@@ -539,27 +553,28 @@ + session->ssh_connection_callback = ssh_server_connection_callback; + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); +- session->socket_callbacks.data=callback_receive_banner; +- session->socket_callbacks.exception=ssh_socket_exception_callback; +- session->socket_callbacks.userdata=session; ++ session->socket_callbacks.data = callback_receive_banner; ++ session->socket_callbacks.exception = ssh_socket_exception_callback; ++ session->socket_callbacks.userdata = session; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } +- pending: ++pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, +- ssh_server_kex_termination,session); ++ ssh_server_kex_termination,session); + SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", +- session->session_state); +- if (rc != SSH_OK) +- return rc; ++ session->session_state); ++ if (rc != SSH_OK) { ++ return rc; ++ } + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { +- return SSH_ERROR; ++ return SSH_ERROR; + } + +- return SSH_OK; ++ return SSH_OK; + } + + /* messages */ +diff --color -ru ../libssh-0.9.6/src/token.c ./src/token.c +--- ../libssh-0.9.6/src/token.c 2021-03-15 08:11:33.000000000 +0100 ++++ ./src/token.c 2023-04-27 12:21:30.260820657 +0200 +@@ -87,7 +87,7 @@ + return NULL; + } + +- tokens->buffer= strdup(chain); ++ tokens->buffer = strdup(chain); + if (tokens->buffer == NULL) { + goto error; + } +diff --color -ru ../libssh-0.9.6/src/wrapper.c ./src/wrapper.c +--- ../libssh-0.9.6/src/wrapper.c 2021-08-26 14:27:44.000000000 +0200 ++++ ./src/wrapper.c 2023-04-27 12:21:30.260820657 +0200 +@@ -147,15 +147,16 @@ + SAFE_FREE(cipher); + } + +-struct ssh_crypto_struct *crypto_new(void) { +- struct ssh_crypto_struct *crypto; ++struct ssh_crypto_struct *crypto_new(void) ++{ ++ struct ssh_crypto_struct *crypto; + +- crypto = malloc(sizeof(struct ssh_crypto_struct)); +- if (crypto == NULL) { +- return NULL; +- } +- ZERO_STRUCTP(crypto); +- return crypto; ++ crypto = malloc(sizeof(struct ssh_crypto_struct)); ++ if (crypto == NULL) { ++ return NULL; ++ } ++ ZERO_STRUCTP(crypto); ++ return crypto; + } + + void crypto_free(struct ssh_crypto_struct *crypto) +diff --color -ru ../libssh-0.9.6/tests/client/torture_rekey.c ./tests/client/torture_rekey.c +--- ../libssh-0.9.6/tests/client/torture_rekey.c 2021-08-26 14:27:44.000000000 +0200 ++++ ./tests/client/torture_rekey.c 2023-04-27 12:24:04.684475780 +0200 +@@ -651,6 +651,92 @@ + #endif /* WITH_SFTP */ + + ++static void setup_server_for_good_guess(void *state) ++{ ++ const char *default_sshd_config = "KexAlgorithms curve25519-sha256"; ++ const char *fips_sshd_config = "KexAlgorithms ecdh-sha2-nistp256"; ++ const char *sshd_config = default_sshd_config; ++ ++ if (ssh_fips_mode()) { ++ sshd_config = fips_sshd_config; ++ } ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++} ++ ++static void torture_rekey_guess_send(void **state) ++{ ++ struct torture_state *s = *state; ++ ++ setup_server_for_good_guess(state); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ torture_rekey_send(state); ++} ++ ++static void torture_rekey_guess_wrong_send(void **state) ++{ ++ struct torture_state *s = *state; ++ const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; ++ ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ torture_rekey_send(state); ++} ++ ++#ifdef WITH_SFTP ++static void torture_rekey_guess_recv(void **state) ++{ ++ struct torture_state *s = *state; ++ int rc; ++ ++ setup_server_for_good_guess(state); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); ++ assert_ssh_return_code(s->ssh.session, rc); ++ ++ session_setup_sftp(state); ++ ++ torture_rekey_recv(state); ++} ++ ++static void torture_rekey_guess_wrong_recv(void **state) ++{ ++ struct torture_state *s = *state; ++ const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; ++ int rc; ++ ++ /* This sets an only supported kex algorithm that we do not have as a first ++ * option */ ++ torture_update_sshd_config(state, sshd_config); ++ ++ /* Make the client send the first_kex_packet_follows flag during key ++ * exchange as well as during the rekey */ ++ s->ssh.session->send_first_kex_follows = true; ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); ++ assert_ssh_return_code(s->ssh.session, rc); ++ ++ session_setup_sftp(state); ++ ++ torture_rekey_recv(state); ++} ++#endif /* WITH_SFTP */ ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -671,19 +757,34 @@ + cmocka_unit_test_setup_teardown(torture_rekey_different_kex, + session_setup, + session_teardown), +- /* Note, that this modifies the sshd_config */ ++ /* TODO verify the two rekey are possible and the states are not broken after rekey */ ++ ++ cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, ++ session_setup, ++ session_teardown), ++ /* Note, that these tests modify the sshd_config so follow-up tests ++ * might get unexpected behavior if they do not update the server with ++ * torture_update_sshd_config() too */ + cmocka_unit_test_setup_teardown(torture_rekey_server_send, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_send, ++ session_setup, ++ session_teardown), ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_send, ++ session_setup, ++ session_teardown), + #ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_server_recv, + session_setup_sftp_server, + session_teardown), +-#endif /* WITH_SFTP */ +- cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_recv, + session_setup, + session_teardown), +- /* TODO verify the two rekey are possible and the states are not broken after rekey */ ++ cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, ++ session_setup, ++ session_teardown), ++#endif /* WITH_SFTP */ + }; + + ssh_init(); diff --git a/SPECS/libssh.spec b/SPECS/libssh.spec index c008ed2..e239015 100644 --- a/SPECS/libssh.spec +++ b/SPECS/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.9.6 -Release: 6%{?dist} +Release: 14%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -13,6 +13,13 @@ Source4: libssh_server.config Patch0: loglevel.patch Patch1: s390x_fix.patch +Patch2: null_dereference_rekey.patch +Patch3: auth_bypass.patch +Patch4: fix_tests.patch +Patch5: covscan23.patch +Patch6: CVE-2023-48795.patch +Patch7: CVE-2023-6004.patch +Patch8: CVE-2023-6918.patch BuildRequires: cmake BuildRequires: doxygen @@ -141,6 +148,43 @@ popd %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config %changelog +* Mon Feb 26 2024 Sahana Prasad - 0.9.6-14 +- Fix CVE-2023-48795 Prefix truncation attack on Binary Packet Protocol (BPP) +- Fix CVE-2023-6918 Missing checks for return values for digests +- Fix CVE-2023-6004 ProxyCommand/ProxyJump features allow injection + of malicious code through hostname +- Note: version is bumped from 12 to 14 directly, as the z-stream + version in 8.9 also has 13. So bumping it to 14, will prevent + upgrade conflicts. +- Resolves:RHEL-19690, RHEL-17244, RHEL-19312 + +* Mon May 15 2023 Norbert Pocs - 0.9.6-12 +- Fix loglevel regression +- Related: rhbz#2182251, rhbz#2189742 + +* Thu May 04 2023 Norbert Pocs - 0.9.6-11 +- .fmf/version is needed to run the tests +- Related: rhbz#2182251, rhbz#2189742 + +* Wed May 03 2023 Norbert Pocs - 0.9.6-10 +- Add missing ci.fmf file +- Related: rhbz#2182251, rhbz#2189742 + +* Wed May 03 2023 Norbert Pocs - 0.9.6-9 +- Fix covscan errors found at gating +- Related: rhbz#2182251, rhbz#2189742 + +* Tue May 02 2023 Norbert Pocs - 0.9.6-8 +- Backport test fixing commits to make the build pass +- Related: rhbz#2182251, rhbz#2189742 + +* Thu Apr 27 2023 Norbert Pocs - 0.9.6-7 +- Fix NULL dereference during rekeying with algorithm guessing + GHSL-2023-032 / CVE-2023-1667 +- Fix possible authentication bypass + GHSL 2023-085 / CVE-2023-2283 +- Resolves: rhbz#2182251, rhbz#2189742 + * Fri Jan 06 2023 Norbert Pocs - 0.9.6-6 - Enable client and server testing build time - Fix failing rekey test on arch s390x