diff --git a/backport-0001-CVE-2023-1667-packet_cb-Log-more-verbose-error-if-si.patch b/backport-0001-CVE-2023-1667-packet_cb-Log-more-verbose-error-if-si.patch new file mode 100644 index 0000000000000000000000000000000000000000..de0f4e43c70fb69e13352193c621101643cc07ce --- /dev/null +++ b/backport-0001-CVE-2023-1667-packet_cb-Log-more-verbose-error-if-si.patch @@ -0,0 +1,35 @@ +From a30339d7b16da7784413e4a4667feb3604ed0458 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 10 Mar 2023 16:14:08 +0100 +Subject: [PATCH] CVE-2023-1667:packet_cb: Log more verbose error if signature + verification fails + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/a30339d7b16da7784413e4a4667feb3604ed0458 + +--- + src/packet_cb.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/packet_cb.c b/src/packet_cb.c +index 39575b1..3e4d5f6 100644 +--- a/src/packet_cb.c ++++ b/src/packet_cb.c +@@ -156,6 +156,9 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){ + 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_PROTOCOL,"Signature verified and valid"); +-- +2.33.0 + diff --git a/backport-0001-CVE-2023-2283-pki_crypto-Fix-possible-authentication.patch b/backport-0001-CVE-2023-2283-pki_crypto-Fix-possible-authentication.patch new file mode 100644 index 0000000000000000000000000000000000000000..d359edb2accfd2c7104a7ac11e1971afc30b85d8 --- /dev/null +++ b/backport-0001-CVE-2023-2283-pki_crypto-Fix-possible-authentication.patch @@ -0,0 +1,103 @@ +From e8dfbb85a28514e1f869dac3000c6cec6cb8d08d Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Mon, 24 Apr 2023 11:51:36 +0200 +Subject: [PATCH] CVE-2023-2283:pki_crypto: Fix possible authentication bypass + +The return value is changed by the call to pki_key_check_hash_compatible +causing the possibility of returning SSH_OK if memory allocation error +happens later in the function. + +The assignment of SSH_ERROR if the verification fails is no longer needed, +because the value of the variable is already SSH_ERROR. + +Signed-off-by: Norbert Pocs +Reviewed-by: Jakub Jelen +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/e8dfbb85a28514e1f869dac3000c6cec6cb8d08d +--- + src/pki_crypto.c | 32 ++++++++++++++++++-------------- + 1 file changed, 18 insertions(+), 14 deletions(-) + +diff --git a/src/pki_crypto.c b/src/pki_crypto.c +index 013f569e..635b82cb 100644 +--- a/src/pki_crypto.c ++++ b/src/pki_crypto.c +@@ -3175,8 +3175,12 @@ int pki_verify_data_signature(ssh_signature signature, + 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 +@@ -3191,8 +3195,8 @@ int pki_verify_data_signature(ssh_signature signature, + } + + /* 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; + } + +@@ -3237,8 +3241,8 @@ int pki_verify_data_signature(ssh_signature signature, + } + + /* 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)); +@@ -3246,28 +3250,28 @@ int pki_verify_data_signature(ssh_signature signature, + } + + #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); +-- +2.33.0 + diff --git a/backport-0002-CVE-2023-1667-packet-Do-not-allow-servers-to-initiat.patch b/backport-0002-CVE-2023-1667-packet-Do-not-allow-servers-to-initiat.patch new file mode 100644 index 0000000000000000000000000000000000000000..b62c4915d0b1d0ad89681c799230b0238ed37bfb --- /dev/null +++ b/backport-0002-CVE-2023-1667-packet-Do-not-allow-servers-to-initiat.patch @@ -0,0 +1,37 @@ +From 247a4a761cfa745ed1090290c5107de6321143c9 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Tue, 14 Mar 2023 11:35:43 +0100 +Subject: [PATCH] CVE-2023-1667:packet: Do not allow servers to initiate + handshake + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/247a4a761cfa745ed1090290c5107de6321143c9 + +--- + src/packet.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/packet.c b/src/packet.c +index 527c5d3..b928121 100644 +--- a/src/packet.c ++++ b/src/packet.c +@@ -366,6 +366,11 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se + * - 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; +-- +2.33.0 + diff --git a/backport-0002-CVE-2023-2283-pki_crypto-Remove-unnecessary-NUL.patch b/backport-0002-CVE-2023-2283-pki_crypto-Remove-unnecessary-NUL.patch new file mode 100644 index 0000000000000000000000000000000000000000..4a0b7dff82d5f1a58ff175b25bf13bf5f416db30 --- /dev/null +++ b/backport-0002-CVE-2023-2283-pki_crypto-Remove-unnecessary-NUL.patch @@ -0,0 +1,33 @@ +From c68a58575b6d0520e342cb3d3796a8fecd66405d Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Mon, 24 Apr 2023 11:55:59 +0200 +Subject: [PATCH] CVE-2023-2283:pki_crypto: Remove unnecessary NULL check + +Signed-off-by: Norbert Pocs +Reviewed-by: Jakub Jelen +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/c68a58575b6d0520e342cb3d3796a8fecd66405d +--- + src/pki_crypto.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/src/pki_crypto.c b/src/pki_crypto.c +index aaa85ba..9f80086 100644 +--- a/src/pki_crypto.c ++++ b/src/pki_crypto.c +@@ -2387,9 +2387,7 @@ int pki_verify_data_signature(ssh_signature signature, + rc = SSH_OK; + + out: +- if (ctx != NULL) { +- EVP_MD_CTX_free(ctx); +- } ++ EVP_MD_CTX_free(ctx); + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +-- +2.33.0 + diff --git a/backport-0003-CVE-2023-1667-kex-Remove-needless-function-argument.patch b/backport-0003-CVE-2023-1667-kex-Remove-needless-function-argument.patch new file mode 100644 index 0000000000000000000000000000000000000000..6e73935180e2fecf729d117e15c05eb02f2f4fe5 --- /dev/null +++ b/backport-0003-CVE-2023-1667-kex-Remove-needless-function-argument.patch @@ -0,0 +1,107 @@ +From 99760776d4552d8e63edd68ba4a7448766517b8c Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Mon, 13 Mar 2023 15:11:25 +0100 +Subject: [PATCH] CVE-2023-1667:kex: Remove needless function argument + +The information if the session is client or server session is already part of +the session structure so this argument only duplicated information. + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/99760776d4552d8e63edd68ba4a7448766517b8c +--- + include/libssh/kex.h | 2 +- + src/client.c | 4 ++-- + src/kex.c | 7 ++++--- + src/server.c | 4 ++-- + 4 files changed, 9 insertions(+), 8 deletions(-) + +diff --git a/include/libssh/kex.h b/include/libssh/kex.h +index 3a1f4a6..2ace69b 100644 +--- a/include/libssh/kex.h ++++ b/include/libssh/kex.h +@@ -33,7 +33,7 @@ struct ssh_kex_struct { + + 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 --git a/src/client.c b/src/client.c +index 4eb798c..954ed39 100644 +--- a/src/client.c ++++ b/src/client.c +@@ -420,7 +420,7 @@ static void ssh_client_connection_callback(ssh_session session) + if (rc != SSH_OK) { + goto error; + } +- rc = ssh_send_kex(session, 0); ++ rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } +@@ -439,7 +439,7 @@ static void ssh_client_connection_callback(ssh_session session) + if (rc != SSH_OK) { + goto error; + } +- rc = ssh_send_kex(session, 0); ++ rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } +diff --git a/src/kex.c b/src/kex.c +index 82071c7..4080a6b 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -830,9 +830,10 @@ int ssh_kex_select_methods (ssh_session session) + + + /* 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 : ++ struct ssh_kex_struct *kex = (session->server ? ++ &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; +@@ -929,7 +930,7 @@ int ssh_send_rekex(ssh_session session) + } + + 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; +diff --git a/src/server.c b/src/server.c +index 080203f..2728d9b 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -366,7 +366,7 @@ static void ssh_server_connection_callback(ssh_session session){ + 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) { ++ if (ssh_send_kex(session) < 0) { + goto error; + } + break; +@@ -379,7 +379,7 @@ static void ssh_server_connection_callback(ssh_session session){ + 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) ++ if (ssh_send_kex(session) < 0) + goto error; + } + ssh_list_kex(&session->next_crypto->client_kex); // log client kex +-- +2.33.0 + diff --git a/backport-0004-CVE-2023-1667-kex-Factor-out-the-kex-mapping-to-inte.patch b/backport-0004-CVE-2023-1667-kex-Factor-out-the-kex-mapping-to-inte.patch new file mode 100644 index 0000000000000000000000000000000000000000..44eb03eff8b574abbe0cd15d63b2ef072ca7863d --- /dev/null +++ b/backport-0004-CVE-2023-1667-kex-Factor-out-the-kex-mapping-to-inte.patch @@ -0,0 +1,101 @@ +From 6df2daea040c47daff0a861a30761092886fe748 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 16 Mar 2023 14:16:11 +0100 +Subject: [PATCH] CVE-2023-1667:kex: Factor out the kex mapping to internal + enum + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/6df2daea040c47daff0a861a30761092886fe748 +--- + src/kex.c | 64 ++++++++++++++++++++++++++++++++----------------------- + 1 file changed, 37 insertions(+), 27 deletions(-) + +diff --git a/src/kex.c b/src/kex.c +index 4080a6b..94ccccf 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -749,6 +749,40 @@ static const char *ssh_find_aead_hmac(const char *cipher) + 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; ++} ++ + /** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ +@@ -786,33 +820,9 @@ int ssh_kex_select_methods (ssh_session session) + session->next_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; +- } ++ kex = session->next_crypto->kex_methods[SSH_KEX]; ++ session->next_crypto->kex_type = kex_select_kex_type(kex); ++ + SSH_LOG(SSH_LOG_INFO, "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], +-- +2.33.0 + diff --git a/backport-0005-CVE-2023-1667-dh-Expose-the-callback-cleanup-functio.patch b/backport-0005-CVE-2023-1667-dh-Expose-the-callback-cleanup-functio.patch new file mode 100644 index 0000000000000000000000000000000000000000..c97e48e1d561d5bee2ffacd070e8bf014076210b --- /dev/null +++ b/backport-0005-CVE-2023-1667-dh-Expose-the-callback-cleanup-functio.patch @@ -0,0 +1,227 @@ +From b759ae557d611ba347392c051504de474a8d9b60 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 17 Mar 2023 14:05:01 +0100 +Subject: [PATCH] CVE-2023-1667:dh: Expose the callback cleanup functions + +These will be helpful when we already sent the first key exchange packet, but we +found out that our guess was wrong and we need to initiate different key +exchange method with different callbacks. + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/b759ae557d611ba347392c051504de474a8d9b60 +--- + include/libssh/curve25519.h | 1 + + include/libssh/dh-gex.h | 1 + + include/libssh/dh.h | 1 + + include/libssh/ecdh.h | 1 + + src/curve25519.c | 7 ++++++- + src/dh-gex.c | 7 ++++++- + src/dh.c | 7 ++++++- + src/ecdh.c | 7 ++++++- + src/kex.c | 38 +++++++++++++++++++++++++++++++++++++ + 9 files changed, 66 insertions(+), 4 deletions(-) + +diff --git a/include/libssh/curve25519.h b/include/libssh/curve25519.h +index f0cc634..77e6c31 100644 +--- a/include/libssh/curve25519.h ++++ b/include/libssh/curve25519.h +@@ -48,6 +48,7 @@ typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; + + + 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 --git a/include/libssh/dh-gex.h b/include/libssh/dh-gex.h +index 4fc23d8..7a91d7d 100644 +--- a/include/libssh/dh-gex.h ++++ b/include/libssh/dh-gex.h +@@ -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 --git a/include/libssh/dh.h b/include/libssh/dh.h +index 390b30d..57f37cd 100644 +--- a/include/libssh/dh.h ++++ b/include/libssh/dh.h +@@ -65,6 +65,7 @@ int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); + + 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 --git a/include/libssh/ecdh.h b/include/libssh/ecdh.h +index 17fe02e..c1f03a9 100644 +--- a/include/libssh/ecdh.h ++++ b/include/libssh/ecdh.h +@@ -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 --git a/src/curve25519.c b/src/curve25519.c +index d251755..3765443 100644 +--- a/src/curve25519.c ++++ b/src/curve25519.c +@@ -172,6 +172,11 @@ int ssh_client_curve25519_init(ssh_session session) + 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 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ + (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 --git a/src/dh-gex.c b/src/dh-gex.c +index 88a9714..4a29854 100644 +--- a/src/dh-gex.c ++++ b/src/dh-gex.c +@@ -238,6 +238,11 @@ error: + 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 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) + (void)user; + SSH_LOG(SSH_LOG_PROTOCOL, "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 --git a/src/dh.c b/src/dh.c +index 18b7173..c265efc 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -342,6 +342,11 @@ error: + 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 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + (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 --git a/src/ecdh.c b/src/ecdh.c +index a4c07cc..e5b11ba 100644 +--- a/src/ecdh.c ++++ b/src/ecdh.c +@@ -43,6 +43,11 @@ struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { + .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 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ + (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 --git a/src/kex.c b/src/kex.c +index 94ccccf..f1dab08 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -783,6 +783,44 @@ kex_select_kex_type(const char *kex) + 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. + */ +-- +2.33.0 + diff --git a/backport-0006-CVE-2023-1667-kex-Correctly-handle-last-fields-of-KE.patch b/backport-0006-CVE-2023-1667-kex-Correctly-handle-last-fields-of-KE.patch new file mode 100644 index 0000000000000000000000000000000000000000..b8514a7e68e08294237a0625d390e9cbb3f6d973 --- /dev/null +++ b/backport-0006-CVE-2023-1667-kex-Correctly-handle-last-fields-of-KE.patch @@ -0,0 +1,303 @@ +From fc1a8bb4555624f85ba1370721ad2086a4feff8c Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 10 Mar 2023 12:59:48 +0100 +Subject: [PATCH] CVE-2023-1667:kex: Correctly handle last fields of KEXINIT + also in the client side + +Previously, the last two fields of KEXINIT were considered as always zero for +the key exchange. This was true for the sending side, but might have not been +true for the received KEXINIT from the peer. + +This moves the construction of these two fields closer to their reading or +writing, instead of hardcoding them on the last possible moment before they go +as input to the hashing function. + +This also allows accepting the first_kex_packet_follows on the client side, even +though there is no kex algorithm now that would allow this. + +It also avoid memory leaks in case the server_set_kex() or ssh_set_client_kex() +gets called multiple times, ensuring the algorithms will not change under our +hands. + +It also makes use of a new flag to track if we sent KEXINIT. + +Previously, this was tracked only implicitly by the content of the +session->next_crypto->{server,client}_kex (local kex). If it was not set, we +considered it was not send. But given that we need to check the local kex even +before sending it when we receive first_kex_packet_follows flag in the KEXINIT, +this can no longer be used. + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/fc1a8bb4555624f85ba1370721ad2086a4feff8c +--- + include/libssh/session.h | 5 ++ + src/client.c | 2 +- + src/kex.c | 124 +++++++++++++++++++++------------------ + src/server.c | 8 ++- + 4 files changed, 80 insertions(+), 59 deletions(-) + +diff --git a/include/libssh/session.h b/include/libssh/session.h +index 03c2bb6..1c33a02 100644 +--- a/include/libssh/session.h ++++ b/include/libssh/session.h +@@ -75,6 +75,11 @@ enum ssh_pending_call_e { + /* 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 +diff --git a/src/client.c b/src/client.c +index 954ed39..20fa33f 100644 +--- a/src/client.c ++++ b/src/client.c +@@ -433,7 +433,7 @@ static void ssh_client_connection_callback(ssh_session session) + 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) { ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + /* in rekeying state if next_crypto client_kex is empty */ + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { +diff --git a/src/kex.c b/src/kex.c +index f1dab08..49aec45 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -345,13 +345,24 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + (void)user; + + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { +- SSH_LOG(SSH_LOG_INFO, "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->dh_handshake_state == DH_STATE_INIT_SENT) { ++ SSH_LOG(SSH_LOG_DEBUG, "Receeved peer kexinit answer"); ++ } else { ++ 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"); + goto error; + } + + if (server_kex) { ++#ifdef WITH_SERVER + len = ssh_buffer_get_data(packet,session->next_crypto->client_kex.cookie, 16); + if (len != 16) { + ssh_set_error(session, SSH_FATAL, "ssh_packet_kexinit: no cookie in packet"); +@@ -363,6 +374,12 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + 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); + if (len != 16) { +@@ -375,6 +392,11 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + 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; ++ } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { +@@ -419,22 +441,37 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + * 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. ++ */ ++ if (first_kex_packet_follows) { ++ char **client_methods = session->next_crypto->client_kex.methods; ++ char **server_methods = session->next_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]); ++ } + ++ if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports + * RFC 8308 extension negotiation. +@@ -507,19 +544,6 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + 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 */ +@@ -672,14 +696,18 @@ int ssh_set_client_kex(ssh_session session) + 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++) { +@@ -924,10 +952,21 @@ int ssh_send_kex(ssh_session session) + goto error; + } + ++ /* Prepare also the first_kex_packet_follows and reserved to 0 */ ++ rc = ssh_buffer_add_u8(session->out_hashbuf, 0); ++ if (rc < 0) { ++ goto error; ++ } ++ rc = ssh_buffer_add_u32(session->out_hashbuf, 0); ++ if (rc < 0) { ++ goto error; ++ } ++ + 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"); + return 0; + error: +@@ -1055,33 +1094,6 @@ int ssh_make_sessionid(ssh_session session) + 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 --git a/src/server.c b/src/server.c +index 2728d9b..fac2e72 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -92,7 +92,11 @@ int server_set_kex(ssh_session session) + 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) { +@@ -375,7 +379,7 @@ static void ssh_server_connection_callback(ssh_session session){ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session,0.6f); +- if(session->next_crypto->server_kex.methods[0]==NULL){ ++ if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + if(server_set_kex(session) == SSH_ERROR) + goto error; + /* We are in a rekeying, so we need to send the server kex */ +-- +2.33.0 + diff --git a/backport-0007-CVE-2023-1667-kex-Add-support-for-sending-first_kex_.patch b/backport-0007-CVE-2023-1667-kex-Add-support-for-sending-first_kex_.patch new file mode 100644 index 0000000000000000000000000000000000000000..e8bde4a446a178619acde7b3faeda6fd6c75b0e0 --- /dev/null +++ b/backport-0007-CVE-2023-1667-kex-Add-support-for-sending-first_kex_.patch @@ -0,0 +1,288 @@ +From 70565ac43867053871f47378c53e5d90ba9007d8 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Thu, 16 Mar 2023 11:55:12 +0100 +Subject: [PATCH] CVE-2023-1667:kex: Add support for sending + first_kex_packet_follows flag + +This is not completely straightforward as it requires us to do some state +shuffling. + +We introduce internal flag that can turn this on in client side, so far for +testing only as we do not want to universally enable this. We also repurpose the +server flag indicating the guess was wrong also for the client to make desired +decisions. + +If we found out our guess was wrong, we need to hope the server was able to +figure out this much, we need to revert the DH FSM state, drop the callbacks +from the "wrong" key exchange method and initiate the right one. + +The server side is already tested by the pkd_hello_i1, which is executing tests +against dropbrear clients, which is using this flag by default out of the box. + +Tested manually also with the pkd_hello --rekey to make sure the server is able +to handle the rekeying with all key exchange methods. + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/70565ac43867053871f47378c53e5d90ba9007d8 +--- + include/libssh/dh.h | 1 + + include/libssh/session.h | 13 +++++-- + src/client.c | 12 +++++- + src/kex.c | 82 +++++++++++++++++++++++++++++++++++----- + 4 files changed, 93 insertions(+), 15 deletions(-) + +diff --git a/include/libssh/dh.h b/include/libssh/dh.h +index 57f37cd..704888c 100644 +--- a/include/libssh/dh.h ++++ b/include/libssh/dh.h +@@ -63,6 +63,7 @@ int ssh_dh_get_current_server_publickey_blob(ssh_session session, + 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); +diff --git a/include/libssh/session.h b/include/libssh/session.h +index 1c33a02..bd8e1b5 100644 +--- a/include/libssh/session.h ++++ b/include/libssh/session.h +@@ -163,14 +163,21 @@ struct ssh_session_struct { + 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; +diff --git a/src/client.c b/src/client.c +index 20fa33f..04f7c53 100644 +--- a/src/client.c ++++ b/src/client.c +@@ -243,10 +243,13 @@ end: + * @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,6 +389,8 @@ static void ssh_client_connection_callback(ssh_session session) + { + int rc; + ++ SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); ++ + switch(session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: +@@ -448,6 +453,9 @@ static void ssh_client_connection_callback(ssh_session session) + goto error; + set_status(session,0.8f); + session->session_state=SSH_SESSION_STATE_DH; ++ ++ /* If the init packet was already sent in previous step, this will be no ++ * operation */ + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } +diff --git a/src/kex.c b/src/kex.c +index 49aec45..5abe330 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -28,6 +28,7 @@ + #include + #include + ++#include "libssh/libssh.h" + #include "libssh/priv.h" + #include "libssh/buffer.h" + #include "libssh/dh.h" +@@ -344,14 +345,19 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + (void)type; + (void)user; + ++ SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); ++ + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { + 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->dh_handshake_state == DH_STATE_INIT_SENT) { +- SSH_LOG(SSH_LOG_DEBUG, "Receeved peer kexinit answer"); +- } else { ++ } 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; +@@ -459,9 +465,10 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + /* + * 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. ++ * must be ignored on the server side. ++ * Client needs to start the Key exchange over with the correct method + */ +- if (first_kex_packet_follows) { ++ if (first_kex_packet_follows || session->send_first_kex_follows) { + char **client_methods = session->next_crypto->client_kex.methods; + char **server_methods = session->next_crypto->server_kex.methods; + session->first_kex_follows_guess_wrong = +@@ -469,6 +476,8 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + 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) { +@@ -548,7 +557,12 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) + + /* 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; + +@@ -858,6 +872,7 @@ int ssh_kex_select_methods (ssh_session session) + struct ssh_kex_struct *client = &session->next_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. +@@ -886,8 +901,18 @@ int ssh_kex_select_methods (ssh_session session) + session->next_crypto->kex_methods[i] = strdup(""); + } + } +- kex = session->next_crypto->kex_methods[SSH_KEX]; +- session->next_crypto->kex_type = kex_select_kex_type(kex); ++ ++ /* 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(session->next_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; ++ } ++ session->next_crypto->kex_type = kex_type; + + SSH_LOG(SSH_LOG_INFO, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + session->next_crypto->kex_methods[SSH_KEX], +@@ -914,6 +939,19 @@ int ssh_send_kex(ssh_session session) + 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", +@@ -946,14 +984,14 @@ int ssh_send_kex(ssh_session session) + + rc = ssh_buffer_pack(session->out_buffer, + "bd", +- 0, ++ first_kex_packet_follows, + 0); + if (rc != SSH_OK) { + goto error; + } + + /* Prepare also the first_kex_packet_follows and reserved to 0 */ +- rc = ssh_buffer_add_u8(session->out_hashbuf, 0); ++ rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } +@@ -968,6 +1006,30 @@ int ssh_send_kex(ssh_session session) + + 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; + error: + ssh_buffer_reinit(session->out_buffer); +-- +2.33.0 + diff --git a/backport-0008-CVE-2023-1667-tests-Client-coverage-for-key-exchange.patch b/backport-0008-CVE-2023-1667-tests-Client-coverage-for-key-exchange.patch new file mode 100644 index 0000000000000000000000000000000000000000..4e6833c9534754f2cc2f552390cec209ee89e4b9 --- /dev/null +++ b/backport-0008-CVE-2023-1667-tests-Client-coverage-for-key-exchange.patch @@ -0,0 +1,154 @@ +From d08f1b2377fead6489aa1d6a102bf65895ecf858 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 17 Mar 2023 14:09:14 +0100 +Subject: [PATCH] CVE-2023-1667:tests: Client coverage for key exchange with + kex guessing + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/d08f1b2377fead6489aa1d6a102bf65895ecf858 +--- + tests/client/torture_rekey.c | 108 +++++++++++++++++++++++++++++++++-- + 1 file changed, 104 insertions(+), 4 deletions(-) + +diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c +index 46a015d..eab4a8b 100644 +--- a/tests/client/torture_rekey.c ++++ b/tests/client/torture_rekey.c +@@ -650,6 +650,91 @@ static void torture_rekey_server_recv(void **state) + } + #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; +@@ -671,19 +756,34 @@ int torture_run_tests(void) { + 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(); +-- +2.33.0 + diff --git a/backport-0009-CVE-2023-1667-tests-Send-a-bit-more-to-make-sure-rek.patch b/backport-0009-CVE-2023-1667-tests-Send-a-bit-more-to-make-sure-rek.patch new file mode 100644 index 0000000000000000000000000000000000000000..b45a548fe1cc10cd143b01cb7966355d0c053503 --- /dev/null +++ b/backport-0009-CVE-2023-1667-tests-Send-a-bit-more-to-make-sure-rek.patch @@ -0,0 +1,40 @@ +From dc1254d53e4fc6cbeb4797fc6ca1c9ed2c21f15c Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Mon, 17 Apr 2023 16:53:35 +0200 +Subject: [PATCH] CVE-2023-1667:tests: Send a bit more to make sure rekey is + completed + +This was for some reason failing on CentOS 7 in 0.10 branch so bringing this to +the master too. + +Signed-off-by: Jakub Jelen +Reviewed-by: Norbert Pocs +Reviewed-by: Andreas Schneider + +Conflict:NA +Reference:https://gitlab.com/libssh/libssh-mirror/commit/dc1254d53e4fc6cbeb4797fc6ca1c9ed2c21f15c +--- + tests/client/torture_rekey.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c +index d9667267..ccd5ae2c 100644 +--- a/tests/client/torture_rekey.c ++++ b/tests/client/torture_rekey.c +@@ -192,10 +192,11 @@ static void torture_rekey_send(void **state) + 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); + } +-- +2.33.0 + diff --git a/libssh.spec b/libssh.spec index 9cbb1aab3bca589c9c664f070489b6a18daf17ec..76a50b0a860c5080ec6a973c1bbb739c58cef4de 100644 --- a/libssh.spec +++ b/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.9.6 -Release: 6 +Release: 7 Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -29,6 +29,17 @@ Patch16: backport-tests-Add-test-for-expanding-port-numbers.patch Patch17: backport-socket-Add-error-message-if-execv-fails.patch Patch18: backport-config-Escape-brackets-in-ProxyCommand-build-from.patch Patch19: backport-packet-do-not-enqueue-outgoing-packets-after-sending.patch +Patch20: backport-0001-CVE-2023-1667-packet_cb-Log-more-verbose-error-if-si.patch +Patch21: backport-0002-CVE-2023-1667-packet-Do-not-allow-servers-to-initiat.patch +Patch22: backport-0003-CVE-2023-1667-kex-Remove-needless-function-argument.patch +Patch23: backport-0004-CVE-2023-1667-kex-Factor-out-the-kex-mapping-to-inte.patch +Patch24: backport-0005-CVE-2023-1667-dh-Expose-the-callback-cleanup-functio.patch +Patch25: backport-0006-CVE-2023-1667-kex-Correctly-handle-last-fields-of-KE.patch +Patch26: backport-0007-CVE-2023-1667-kex-Add-support-for-sending-first_kex_.patch +Patch27: backport-0008-CVE-2023-1667-tests-Client-coverage-for-key-exchange.patch +Patch28: backport-0009-CVE-2023-1667-tests-Send-a-bit-more-to-make-sure-rek.patch +Patch29: backport-0001-CVE-2023-2283-pki_crypto-Fix-possible-authentication.patch +Patch30: backport-0002-CVE-2023-2283-pki_crypto-Remove-unnecessary-NUL.patch BuildRequires: cmake gcc-c++ gnupg2 openssl-devel pkgconfig zlib-devel BuildRequires: krb5-devel libcmocka-devel openssh-clients openssh-server @@ -114,6 +125,12 @@ popd %doc ChangeLog README %changelog +* Fri May 19 2023 renmingshuai - 0.9.6-7 +- Type:CVE +- Id:CVE-2023-1667,CVE-2023-2283 +- SUG:NA +- DESC:fix CVE-2023-1667 and CVE-2023-2283 + * Tue Mar 28 2023 renmingshuai - 0.9.6-6 - Type:bugfix - Id:NA