diff --git a/CVE-2023-48795.patch b/CVE-2023-48795.patch new file mode 100644 index 0000000..392addd --- /dev/null +++ b/CVE-2023-48795.patch @@ -0,0 +1,729 @@ +From 4cef5e965a46e9271aed62631b152e4bd23c1e3c 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 7f10a709..f0c8cb20 100644 +--- a/include/libssh/packet.h ++++ b/include/libssh/packet.h +@@ -67,6 +67,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 eb14e97a..97936195 100644 +--- a/include/libssh/session.h ++++ b/include/libssh/session.h +@@ -81,6 +81,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 66291b5f..4aeb4756 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 91617081..642a88ae 100644 +--- a/src/dh-gex.c ++++ b/src/dh-gex.c +@@ -297,15 +297,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 011d97b3..e19e43d1 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -398,16 +398,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: +@@ -551,15 +545,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 51084b7a..069b1372 100644 +--- a/src/ecdh_crypto.c ++++ b/src/ecdh_crypto.c +@@ -619,18 +619,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 235f2904..3d9d426f 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 cfe017a0..dda73922 100644 +--- a/src/ecdh_mbedcrypto.c ++++ b/src/ecdh_mbedcrypto.c +@@ -318,16 +318,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 b9455d2d..3818297b 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -188,6 +188,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,"\ +@@ -516,6 +519,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 +@@ -792,21 +816,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 eb7eb42a..ea73f9ad 100644 +--- a/src/packet.c ++++ b/src/packet.c +@@ -1314,6 +1314,19 @@ 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; +@@ -1354,6 +1379,9 @@ 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; + } +@@ -1529,7 +1557,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 +@@ -1842,6 +1896,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 0ecf8771..2f364c26 100644 +--- a/src/packet_cb.c ++++ b/src/packet_cb.c +@@ -115,6 +115,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 0870c8db28be9eb457ee3d4f9a168959d9507efd 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 ede7fa8a..ba98fded 100644 +--- a/include/libssh/kex.h ++++ b/include/libssh/kex.h +@@ -40,6 +40,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 3818297b..9ad671db 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -763,11 +763,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. */ +@@ -816,11 +813,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; +@@ -830,9 +849,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 dc070a73..70b90899 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 5846e57538c750c5ce67df887d09fa99861c79c6 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 9ad671db..fbc70cf4 100644 +--- a/src/kex.c ++++ b/src/kex.c +@@ -961,11 +961,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 89df759200d31fc79fbbe213d8eda0d329eebf6d 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 | 55 ++++++++++++++++++++---------------- + 1 file changed, 31 insertions(+), 24 deletions(-) + +diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c +index ccd5ae2c..57e03e3f 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); +@@ -468,15 +480,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 -up libssh-0.10.4/src/packet.c.CVE-2023-48795.patch libssh-0.10.4/src/packet.c +--- libssh-0.10.4/src/packet.c.CVE-2023-48795.patch 2024-01-09 05:46:53.064383034 +0100 ++++ libssh-0.10.4/src/packet.c 2024-01-09 05:48:28.360340184 +0100 +@@ -1345,6 +1345,19 @@ size_t ssh_packet_socket_callback(const + "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); + +diff -up libssh-0.10.4/tests/client/torture_rekey.c.CVE-2023-48795.patch libssh-0.10.4/tests/client/torture_rekey.c +--- libssh-0.10.4/tests/client/torture_rekey.c.CVE-2023-48795.patch 2024-01-09 18:48:16.684366049 +0100 ++++ libssh-0.10.4/tests/client/torture_rekey.c 2024-01-09 18:50:49.135897268 +0100 +@@ -290,14 +290,10 @@ static void torture_rekey_recv(void **st + mode_t mask; + int 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); diff --git a/CVE-2023-6004.patch b/CVE-2023-6004.patch new file mode 100644 index 0000000..135f035 --- /dev/null +++ b/CVE-2023-6004.patch @@ -0,0 +1,1112 @@ +From c2c56bacab00766d01671413321d564227aabf19 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 +Reviewed-by: Jakub Jelen +--- + tests/unittests/torture_config.c | 56 +++++++++++++++++--------------- + 1 file changed, 30 insertions(+), 26 deletions(-) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index 406f1985..b7c763af 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -995,23 +995,22 @@ static void torture_config_proxyjump(void **state, + assert_string_equal(session->opts.ProxyCommand, + "ssh -W [%h]:%p 2620:52:0::fed"); + +- /* In this part, we try various other config files and strings. */ +- +- /* Try to create some invalid configurations */ +- /* Non-numeric port */ +- config = "Host bad-port\n" +- "\tProxyJump jumpbox:22bad22\n"; ++ /* Multiple @ is allowed in second jump */ ++ config = "Host allowed-hostname\n" ++ "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); +- _parse_config(session, file, string, SSH_ERROR); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); ++ _parse_config(session, file, string, SSH_OK); ++ assert_string_equal(session->opts.ProxyCommand, ++ "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); + +- /* Too many @ */ +- config = "Host bad-hostname\n" ++ /* Multiple @ is allowed */ ++ config = "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); +@@ -1019,7 +1018,24 @@ static void torture_config_proxyjump(void **state, + string = config; + } + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); ++ _parse_config(session, file, string, SSH_OK); ++ 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 */ ++ config = "Host bad-port\n" ++ "\tProxyJump jumpbox:22bad22\n"; ++ if (file != NULL) { ++ torture_write_file(file, config); ++ } else { ++ string = config; ++ } ++ torture_reset_config(session); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + _parse_config(session, file, string, SSH_ERROR); + + /* Braces mismatch in hostname */ +@@ -1094,18 +1110,6 @@ static void torture_config_proxyjump(void **state, + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); + _parse_config(session, file, string, SSH_ERROR); + +- /* Too many @ in second jump */ +- config = "Host bad-hostname\n" +- "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; +- if (file != NULL) { +- torture_write_file(file, config); +- } else { +- string = config; +- } +- torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); +- _parse_config(session, file, string, SSH_ERROR); +- + /* Braces mismatch in second jump */ + config = "Host mismatch\n" + "\tProxyJump localhost,[::1:20\n"; +-- +2.41.0 + + +From a66b4a6eae6614d200a3625862d77565b96a7cd3 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 +Reviewed-by: Jakub Jelen +--- + 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 0d988fec..cf83e2c5 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -180,7 +180,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 8615c24647f773a5e04203c7459512715d698be1 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 +Reviewed-by: Jakub Jelen +--- + src/options.c | 40 ++++++++++++++++------------------------ + 1 file changed, 16 insertions(+), 24 deletions(-) + +diff --git a/src/options.c b/src/options.c +index 6f2c9397..38511455 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -37,6 +37,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" +@@ -515,33 +516,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 = strrchr(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 c6180409677c765e6b9ae2b18a3a7a9671ac1dbe 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 +Reviewed-by: Jakub Jelen +--- + include/libssh/misc.h | 3 ++ + src/misc.c | 68 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 71 insertions(+) + +diff --git a/src/misc.c b/src/misc.c +index 7c478a77..be6ee836 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -1974,4 +1976,70 @@ char *ssh_strerror(int err_num, char *buf, size_t buflen) + #endif /* defined(__linux__) && defined(__GLIBC__) && defined(_GNU_SOURCE) */ + } + ++/** ++ * @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 9bbb817c0c5434f03613d0783b2ef5f52235b901 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 +Reviewed-by: Jakub Jelen +--- + 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 9e346ff8..e682b6d4 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -760,6 +760,78 @@ static void torture_ssh_strerror(void **state) + assert_non_null(out); + } + ++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[] = { +@@ -784,6 +856,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_quote_file_name), + cmocka_unit_test(torture_ssh_strreplace), + cmocka_unit_test(torture_ssh_strerror), ++ cmocka_unit_test(torture_ssh_check_hostname_syntax), + }; + + ssh_init(); +-- +2.41.0 + + +From 22492b69bba22b102342afc574800d354a08e405 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 +Reviewed-by: Jakub Jelen +--- + src/config_parser.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/config_parser.c b/src/config_parser.c +index cf83e2c5..b8b94611 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" + + /* Returns the original string after skipping the leading whitespace + * and optional quotes. +@@ -167,6 +168,7 @@ int ssh_config_parse_uri(const char *tok, + { + char *endp = NULL; + long port_n; ++ int rc; + + /* Sanitize inputs */ + if (username != NULL) { +@@ -224,6 +226,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 d7467498fd988949edde9c6384973250fd454a8b 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 +Reviewed-by: Jakub Jelen +--- + 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 9b8019ca..1bad4ccc 100644 +--- a/tests/client/torture_proxycommand.c ++++ b/tests/client/torture_proxycommand.c +@@ -166,6 +166,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[] = { +@@ -181,6 +231,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 62d3101c1f76b6891b70c50154e0e934d6b8cb57 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 +Reviewed-by: Jakub Jelen +--- + 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 e682b6d4..b07392ab 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -832,6 +832,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[] = { +@@ -857,6 +882,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_strreplace), + cmocka_unit_test(torture_ssh_strerror), + cmocka_unit_test(torture_ssh_check_hostname_syntax), ++ cmocka_unit_test(torture_ssh_is_ipaddr), + }; + + ssh_init(); +-- +2.41.0 + + +From cea841d71c025f9c998b7d5fc9f2a2839df62921 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 +Reviewed-by: Jakub Jelen +--- + src/CMakeLists.txt | 1 + + src/connect.c | 2 +- + src/misc.c | 44 ++++++++++++++++++++++++++++++++++++++------ + 3 files changed, 40 insertions(+), 7 deletions(-) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index d6245c0d..807313b5 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -91,6 +91,7 @@ endif() + if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} ++ iphlpapi + ws2_32 + ) + endif (WIN32) +diff --git a/src/connect.c b/src/connect.c +index 57e37e63..15cae644 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 be6ee836..7081f12a 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 +@@ -222,22 +224,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 */ +@@ -343,17 +360,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 2c492ee179d5caa2718c5e768bab6e0b2b64a8b0 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 +Reviewed-by: Jakub Jelen +--- + 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 b07392ab..77166759 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -17,7 +17,14 @@ + #include "torture.h" + #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"; + +@@ -834,14 +841,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 1a02364b5107a4125ea3cb76fcdb6beabaebf3be 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 a7dd42a2..ca353432 100644 +--- a/include/libssh/config_parser.h ++++ b/include/libssh/config_parser.h +@@ -30,6 +30,8 @@ + #ifndef CONFIG_PARSER_H_ + #define CONFIG_PARSER_H_ + ++#include ++ + char *ssh_config_get_cmd(char **str); + + char *ssh_config_get_token(char **str); +diff --git a/src/config.c b/src/config.c +index c5c40125..273db7c8 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -464,7 +464,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); +@@ -475,7 +475,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 b8b94611..d4b2d2c3 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -162,9 +162,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; +@@ -210,12 +211,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 38511455..b3ecffe1 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -516,17 +516,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 6f1b1e76bb38bc89819132e1810e4301ec9034a4 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 | 16 ++++++++++ + 2 files changed, 65 insertions(+) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index b7c763af..26a24215 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -1850,6 +1850,53 @@ static void torture_config_make_absolute_no_sshdir(void **state) + torture_config_make_absolute_int(state, 1); + } + ++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; +@@ -1922,6 +1969,8 @@ int torture_run_tests(void) + setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, teardown), ++ cmocka_unit_test_setup_teardown(torture_config_parse_uri, ++ setup, teardown), + }; + + +diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c +index 425d7295..8a5505a1 100644 +--- a/tests/unittests/torture_options.c ++++ b/tests/unittests/torture_options.c +@@ -60,6 +60,20 @@ 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); +@@ -67,12 +81,14 @@ static void torture_options_set_host(void **state) { + 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 -up libssh-0.10.4/include/libssh/config_parser.h.CVE-2023-6004.patch libssh-0.10.4/include/libssh/config_parser.h +--- libssh-0.10.4/include/libssh/config_parser.h.CVE-2023-6004.patch 2024-01-08 19:48:48.964006647 +0100 ++++ libssh-0.10.4/include/libssh/config_parser.h 2024-01-08 19:56:38.850726165 +0100 +@@ -47,13 +47,16 @@ int ssh_config_get_yesno(char **str, int + * 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 -up libssh-0.10.4/include/libssh/misc.h.CVE-2023-6004.patch libssh-0.10.4/include/libssh/misc.h +--- libssh-0.10.4/include/libssh/misc.h.CVE-2023-6004.patch 2024-01-08 19:48:07.422589393 +0100 ++++ libssh-0.10.4/include/libssh/misc.h 2024-01-08 19:53:33.830867829 +0100 +@@ -99,4 +99,7 @@ int ssh_newline_vis(const char *string, + int ssh_tmpname(char *template); + + char *ssh_strreplace(const char *src, const char *pattern, const char *repl); ++ ++int ssh_check_hostname_syntax(const char *hostname); ++ + #endif /* MISC_H_ */ +diff -up libssh-0.10.4/src/misc.c.CVE-2023-6004.patch libssh-0.10.4/src/misc.c +--- libssh-0.10.4/src/misc.c.CVE-2023-6004.patch 2024-01-08 19:48:26.779783823 +0100 ++++ libssh-0.10.4/src/misc.c 2024-01-08 19:54:55.209685193 +0100 +@@ -96,6 +96,8 @@ + #define ZLIB_STRING "" + #endif + ++#define ARPA_DOMAIN_MAX_LEN 63 ++ + /** + * @defgroup libssh_misc The SSH helper functions. + * @ingroup libssh diff --git a/CVE-2023-6918.patch b/CVE-2023-6918.patch new file mode 100644 index 0000000..6196216 --- /dev/null +++ b/CVE-2023-6918.patch @@ -0,0 +1,1581 @@ +From 610d7a09f99c601224ae2aa3d3de7e75b1d284dd 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 44f06631..987ae972 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, +- uint8_t key_type, unsigned char *output, ++ unsigned char *key, ++ size_t key_len, ++ uint8_t key_type, ++ unsigned char *output, + size_t requested_len) + { + /* Can't use VLAs with Visual Studio, so allocate the biggest +-- +2.41.0 + + +From 63ff242131c8e6d98917456f71f6d33b9ef3a763 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 | 4 -- + src/libcrypto.c | 54 ------------------------- + src/libgcrypt.c | 53 ------------------------ + src/libmbedcrypto.c | 74 ---------------------------------- + 7 files changed, 192 deletions(-) + +diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h +index 16e5f98f..87f30a4d 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 EVP_MD_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 e4087fd2..c6afc22e 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 e6fc393c..540a025b 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 36589cff..07e64018 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -95,10 +95,6 @@ void sha512_update(SHA512CTX c, const void *data, size_t len); + void sha512_final(unsigned char *md,SHA512CTX c); + void sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +-void evp(int nid, unsigned char *digest, size_t len, unsigned char *hash, unsigned int *hlen); +-EVPCTX evp_init(int nid); +-void evp_update(EVPCTX ctx, const void *data, size_t len); +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); + + HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); + int hmac_update(HMACCTX c, const void *data, size_t len); +diff --git a/src/libcrypto.c b/src/libcrypto.c +index ebdca6e0..4f945d90 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -125,60 +125,6 @@ ENGINE *pki_get_engine(void) + return engine; + } + +-#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, size_t 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, size_t 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 /* HAVE_OPENSSL_ECC */ +- + #ifdef HAVE_OPENSSL_EVP_KDF_CTX + #if OPENSSL_VERSION_NUMBER < 0x30000000L + 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 2e44a53c..f410d997 100644 +--- a/src/libgcrypt.c ++++ b/src/libgcrypt.c +@@ -69,59 +69,6 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { + void ssh_reseed(void){ + } + +-#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, size_t 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, size_t 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 +- + int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, +diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c +index 594e5369..caa3b6e9 100644 +--- a/src/libmbedcrypto.c ++++ b/src/libmbedcrypto.c +@@ -51,80 +51,6 @@ void ssh_reseed(void) + mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); + } + +-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, size_t 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, size_t len) +-{ +- mbedtls_md_update(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- *mdlen = mbedtls_md_get_size(ctx->MBEDTLS_PRIVATE(md_info)); +- mbedtls_md_finish(ctx, md); +- mbedtls_md_free(ctx); +- SAFE_FREE(ctx); +-} +- + int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, +-- +2.41.0 + + +From 8b66d037d575e5f3ce4d35964547ff8c7e75ff8e 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/md_crypto.c | 161 ++++++++++++++++++++++++++++++-------- + src/md_gcrypt.c | 107 +++++++++++++++++++++---- + src/md_mbedcrypto.c | 165 +++++++++++++++++++++++++++++++-------- + src/session.c | 72 ++++++++++++----- + 6 files changed, 504 insertions(+), 131 deletions(-) + +diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h +index 07e64018..b3e28eac 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -72,29 +72,33 @@ 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, size_t len); +-void md5_final(unsigned char *md,MD5CTX c); ++void md5_ctx_free(MD5CTX); ++int md5_update(MD5CTX c, const void *data, size_t len); ++int md5_final(unsigned char *md, MD5CTX c); + + SHACTX sha1_init(void); +-void sha1_update(SHACTX c, const void *data, size_t len); +-void sha1_final(unsigned char *md,SHACTX c); +-void sha1(const unsigned char *digest,size_t len,unsigned char *hash); ++void sha1_ctx_free(SHACTX); ++int sha1_update(SHACTX c, const void *data, size_t len); ++int sha1_final(unsigned char *md,SHACTX c); ++int sha1(const unsigned char *digest,size_t len, unsigned char *hash); + + SHA256CTX sha256_init(void); +-void sha256_update(SHA256CTX c, const void *data, size_t len); +-void sha256_final(unsigned char *md,SHA256CTX c); +-void sha256(const unsigned char *digest, size_t len, unsigned char *hash); ++void sha256_ctx_free(SHA256CTX); ++int sha256_update(SHA256CTX c, const void *data, size_t len); ++int sha256_final(unsigned char *md,SHA256CTX c); ++int sha256(const unsigned char *digest, size_t len, unsigned char *hash); + + SHA384CTX sha384_init(void); +-void sha384_update(SHA384CTX c, const void *data, size_t len); +-void sha384_final(unsigned char *md,SHA384CTX c); +-void sha384(const unsigned char *digest, size_t len, unsigned char *hash); ++void sha384_ctx_free(SHA384CTX); ++int sha384_update(SHA384CTX c, const void *data, size_t len); ++int sha384_final(unsigned char *md,SHA384CTX c); ++int sha384(const unsigned char *digest, size_t len, unsigned char *hash); + + SHA512CTX sha512_init(void); +-void sha512_update(SHA512CTX c, const void *data, size_t len); +-void sha512_final(unsigned char *md,SHA512CTX c); +-void sha512(const unsigned char *digest, size_t len, unsigned char *hash); +- ++void sha512_ctx_free(SHA512CTX); ++int sha512_update(SHA512CTX c, const void *data, size_t len); ++int sha512_final(unsigned char *md,SHA512CTX c); ++int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + + HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); + int hmac_update(HMACCTX c, const void *data, size_t len); +diff --git a/src/kdf.c b/src/kdf.c +index 987ae972..a8e534e5 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, +@@ -126,6 +149,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char digest[DIGEST_MAX_LEN]; + size_t output_len = crypto->digest_len; + ssh_mac_ctx ctx; ++ int rc; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; +@@ -136,11 +160,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, &key_type, 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, &key_type, 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; +@@ -152,10 +195,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/md_crypto.c b/src/md_crypto.c +index f5104f04..f7cda8dd 100644 +--- a/src/md_crypto.c ++++ b/src/md_crypto.c +@@ -25,6 +25,7 @@ + #include "libssh/crypto.h" + #include "libssh/wrapper.h" + ++#include + #include + #include + #include +@@ -46,28 +47,49 @@ sha1_init(void) + } + + void ++sha1_ctx_free(SHACTX c) ++{ ++ EVP_MD_CTX_free(c); ++} ++ ++int + sha1_update(SHACTX c, const void *data, size_t len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++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_free(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha1(const unsigned char *digest, size_t 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) { ++ EVP_MD_CTX_free(c); ++ return SSH_ERROR; ++ } ++ return sha1_final(hash, c); + } + + SHA256CTX +@@ -87,28 +109,49 @@ sha256_init(void) + } + + void ++sha256_ctx_free(SHA256CTX c) ++{ ++ EVP_MD_CTX_free(c); ++} ++ ++int + sha256_update(SHA256CTX c, const void *data, size_t len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++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_free(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha256(const unsigned char *digest, size_t 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) { ++ EVP_MD_CTX_free(c); ++ return SSH_ERROR; + } ++ return sha256_final(hash, c); + } + + SHA384CTX +@@ -128,28 +171,49 @@ sha384_init(void) + } + + void ++sha384_ctx_free(SHA384CTX c) ++{ ++ EVP_MD_CTX_free(c); ++} ++ ++int + sha384_update(SHA384CTX c, const void *data, size_t len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++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_free(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha384(const unsigned char *digest, size_t 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) { ++ EVP_MD_CTX_free(c); ++ return SSH_ERROR; ++ } ++ return sha384_final(hash, c); + } + + SHA512CTX +@@ -169,28 +233,49 @@ sha512_init(void) + } + + void ++sha512_ctx_free(SHA512CTX c) ++{ ++ EVP_MD_CTX_free(c); ++} ++ ++int + sha512_update(SHA512CTX c, const void *data, size_t len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++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_free(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha512(const unsigned char *digest, size_t 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) { ++ EVP_MD_CTX_free(c); ++ return SSH_ERROR; + } ++ return sha512_final(hash, c); + } + + MD5CTX +@@ -210,16 +295,30 @@ md5_init(void) + } + + void ++md5_ctx_free(MD5CTX c) ++{ ++ EVP_MD_CTX_free(c); ++} ++ ++int + md5_update(MD5CTX c, const void *data, size_t len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++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_free(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } +diff --git a/src/md_gcrypt.c b/src/md_gcrypt.c +index 1f0a71f3..93c7b0d9 100644 +--- a/src/md_gcrypt.c ++++ b/src/md_gcrypt.c +@@ -36,24 +36,40 @@ sha1_init(void) + return ctx; + } + +-void ++int + sha1_update(SHACTX c, const void *data, size_t len) + { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + + void ++sha1_ctx_free(SHACTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int + sha1_final(unsigned char *md, SHACTX c) + { ++ unsigned char *tmp = NULL; ++ + gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); ++ 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 ++int + sha1(const unsigned char *digest, size_t len, unsigned char *hash) + { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); ++ return SSH_OK; + } + + SHA256CTX +@@ -66,23 +82,39 @@ sha256_init(void) + } + + void ++sha256_ctx_free(SHA256CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int + sha256_update(SHACTX c, const void *data, size_t len) + { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void ++int + sha256_final(unsigned char *md, SHACTX c) + { ++ unsigned char *tmp = NULL; ++ + gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); ++ 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 ++int + sha256(const unsigned char *digest, size_t len, unsigned char *hash) + { + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); ++ return SSH_OK; + } + + SHA384CTX +@@ -95,23 +127,39 @@ sha384_init(void) + } + + void ++sha384_ctx_free(SHA384CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int + sha384_update(SHACTX c, const void *data, size_t len) + { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void ++int + sha384_final(unsigned char *md, SHACTX c) + { ++ unsigned char *tmp = NULL; ++ + gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); ++ 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 ++int + sha384(const unsigned char *digest, size_t len, unsigned char *hash) + { + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); ++ return SSH_OK; + } + + SHA512CTX +@@ -124,23 +172,39 @@ sha512_init(void) + } + + void ++sha512_ctx_free(SHA512CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int + sha512_update(SHACTX c, const void *data, size_t len) + { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void ++int + sha512_final(unsigned char *md, SHACTX c) + { ++ unsigned char *tmp = NULL; ++ + gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); ++ 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 ++int + sha512(const unsigned char *digest, size_t len, unsigned char *hash) + { + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); ++ return SSH_OK; + } + + MD5CTX +@@ -153,15 +217,30 @@ md5_init(void) + } + + void ++md5_ctx_free(MD5CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int + md5_update(MD5CTX c, const void *data, size_t len) + { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void ++int + md5_final(unsigned char *md, MD5CTX c) + { ++ unsigned char *tmp = NULL; ++ + gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); ++ 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; + } +diff --git a/src/md_mbedcrypto.c b/src/md_mbedcrypto.c +index 227e20ab..b3529b4b 100644 +--- a/src/md_mbedcrypto.c ++++ b/src/md_mbedcrypto.c +@@ -64,27 +64,48 @@ sha1_init(void) + } + + void ++sha1_ctx_free(SHACTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} ++ ++int + sha1_update(SHACTX c, const void *data, size_t len) + { +- mbedtls_md_update(c, data, len); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha1_final(unsigned char *md, SHACTX c) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_finish(c, md); ++ sha1_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha1(const unsigned char *digest, size_t 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 +@@ -122,27 +143,48 @@ sha256_init(void) + } + + void ++sha256_ctx_free(SHA256CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} ++ ++int + sha256_update(SHA256CTX c, const void *data, size_t len) + { +- mbedtls_md_update(c, data, len); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha256_final(unsigned char *md, SHA256CTX c) + { +- mbedtls_md_finish(c, md); ++ int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha256(const unsigned char *digest, size_t len, unsigned char *hash) + { ++ int rc; + 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); ++ 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 +@@ -180,27 +222,48 @@ sha384_init(void) + } + + void ++sha384_ctx_free(SHA384CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} ++ ++int + sha384_update(SHA384CTX c, const void *data, size_t len) + { +- mbedtls_md_update(c, data, len); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha384_final(unsigned char *md, SHA384CTX c) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_finish(c, md); ++ sha384_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha384(const unsigned char *digest, size_t 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 +@@ -237,27 +300,48 @@ sha512_init(void) + } + + void ++sha512_ctx_free(SHA512CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} ++ ++int + sha512_update(SHA512CTX c, const void *data, size_t len) + { +- mbedtls_md_update(c, data, len); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha512_final(unsigned char *md, SHA512CTX c) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_finish(c, md); ++ sha512_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + sha512(const unsigned char *digest, size_t 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 +@@ -294,15 +378,30 @@ md5_init(void) + } + + void ++md5_ctx_free(MD5CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} ++ ++int + md5_update(MD5CTX c, const void *data, size_t len) + { +- mbedtls_md_update(c, data, len); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void ++int + md5_final(unsigned char *md, MD5CTX c) + { +- mbedtls_md_finish(c, md); ++ int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } +diff --git a/src/session.c b/src/session.c +index fe998dee..8c509699 100644 +--- a/src/session.c ++++ b/src/session.c +@@ -1024,7 +1024,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; + } + +@@ -1039,25 +1050,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; +@@ -1177,8 +1184,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; + } +@@ -1200,8 +1216,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; + } +@@ -1231,8 +1256,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 8977e246b6d7ae467cab008a49e0a9e3d84bc2a0 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 a8e534e5..6bc477ce 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 622421018b58392ffecc29726b947e089b678221 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 27e8fc86..c437d421 100644 +--- a/tests/client/torture_session.c ++++ b/tests/client/torture_session.c +@@ -391,6 +391,38 @@ static void torture_freed_channel_get_exit_status(void **state) + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + } + ++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[] = { +@@ -421,6 +453,9 @@ int torture_run_tests(void) { + cmocka_unit_test_setup_teardown(torture_freed_channel_get_exit_status, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_pubkey_hash, ++ session_setup, ++ session_teardown), + }; + + ssh_init(); +-- +2.41.0 + diff --git a/escape-brackets-in-proxycommand.patch b/escape-brackets-in-proxycommand.patch new file mode 100644 index 0000000..2bf8afe --- /dev/null +++ b/escape-brackets-in-proxycommand.patch @@ -0,0 +1,94 @@ +From bccb8513fa4a836aef0519d65eb33bb212606fe1 Mon Sep 17 00:00:00 2001 +From: Thomas Baag +Date: Wed, 21 Sep 2022 20:55:27 +0200 +Subject: [PATCH] config: Escape brackets in ProxyCommand build from ProxyJump + +Missing escaping results in syntax errors in Zsh shell because of square +brackets getting interpreted as being a pattern for globbing. + +Signed-off-by: Thomas Baag +Reviewed-by: Jakub Jelen +--- + src/config.c | 2 +- + tests/unittests/torture_config.c | 14 +++++++------- + 2 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/src/config.c b/src/config.c +index c048418ec..308928429 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -491,7 +491,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) + if (hostname != NULL && do_parsing) { + char com[512] = {0}; + +- rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W [%%h]:%%p %s", ++ rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", + username ? " -l " : "", + username ? username : "", + port ? " -p " : "", +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index 31dadae37..5ff20c99a 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -649,7 +649,7 @@ static void torture_config_unknown(void **state, + /* test corner cases */ + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -W [%h]:%p many-spaces.com"); ++ "ssh -W '[%h]:%p' many-spaces.com"); + assert_string_equal(session->opts.host, "equal.sign"); + + ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); +@@ -945,28 +945,28 @@ static void torture_config_proxyjump(void **state, + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); +- assert_string_equal(session->opts.ProxyCommand, "ssh -W [%h]:%p jumpbox"); ++ assert_string_equal(session->opts.ProxyCommand, "ssh -W '[%h]:%p' jumpbox"); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -l user -W [%h]:%p jumpbox"); ++ "ssh -l user -W '[%h]:%p' jumpbox"); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -p 2222 -W [%h]:%p jumpbox"); ++ "ssh -p 2222 -W '[%h]:%p' jumpbox"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -l u1 -p 222 -J u2@second:33 -W [%h]:%p first"); ++ "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); + + /* none */ + torture_reset_config(session); +@@ -985,14 +985,14 @@ static void torture_config_proxyjump(void **state, + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -W [%h]:%p jumpbox"); ++ "ssh -W '[%h]:%p' jumpbox"); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, +- "ssh -W [%h]:%p 2620:52:0::fed"); ++ "ssh -W '[%h]:%p' 2620:52:0::fed"); + + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" +-- +GitLab + diff --git a/libssh.spec b/libssh.spec index 14d14ba..c1af249 100644 --- a/libssh.spec +++ b/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.10.4 -Release: 11%{?dist} +Release: 12%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -51,6 +51,10 @@ Patch9: auth_bypass.patch Patch10: covscan23.patch Patch11: rekey_test_fixup.patch Patch12: covscan23_1.patch +Patch13: CVE-2023-6004.patch +Patch14: CVE-2023-48795.patch +Patch15: CVE-2023-6918.patch +Patch16: escape-brackets-in-proxycommand.patch %description The ssh library was designed to be used by programmers needing a working SSH @@ -143,6 +147,13 @@ popd %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config %changelog +* Tue Jan 09 2024 Sahana Prasad - 0.10.4-12 +- 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 +- Resolves: RHEL-19310, RHEL-19691, RHEL-17245 + * Wed Jun 21 2023 Norbert Pocs - 0.10.4-11 - Fix loglevel regression - Related: rhbz#2182252, rhbz#2189740