diff --git a/SOURCES/CVE-2023-6004.patch b/SOURCES/CVE-2023-6004.patch new file mode 100644 index 0000000..27a72d6 --- /dev/null +++ b/SOURCES/CVE-2023-6004.patch @@ -0,0 +1,1114 @@ +From 11bd6e6ad926a38cd7b9f8308a4c2fd8dfd9200c Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Sun, 5 Nov 2023 13:12:47 +0100 +Subject: [PATCH 01/12] CVE-2023-6004: torture_config: Allow multiple '@' in + usernames + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_config.c | 44 ++++++++++++++++++-------------- + 1 file changed, 25 insertions(+), 19 deletions(-) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index f91112a9..3a5a74bf 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -671,24 +671,40 @@ static void torture_config_proxyjump(void **state) { + assert_string_equal(session->opts.ProxyCommand, + "ssh -W [%h]:%p 2620:52:0::fed"); + +- /* Try to create some invalid configurations */ +- /* Non-numeric port */ ++ /* Multiple @ is allowed in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-port\n" +- "\tProxyJump jumpbox:22bad22\n" ++ "Host allowed-hostname\n" ++ "\tProxyJump localhost,user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); +- assert_ssh_return_code_equal(session, ret, SSH_ERROR); ++ assert_ssh_return_code(session, ret); ++ assert_string_equal(session->opts.ProxyCommand, ++ "ssh -J user@principal.com@jumpbox:22 -W [%h]:%p localhost"); + +- /* Too many @ */ ++ /* Multiple @ is allowed */ + torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-hostname\n" ++ "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n" + ""); + torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); ++ ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); ++ assert_ssh_return_code(session, ret); ++ assert_string_equal(session->opts.ProxyCommand, ++ "ssh -l user@principal.com -p 22 -W [%h]:%p jumpbox"); ++ ++ /* In this part, we try various other config files and strings. */ ++ ++ /* Try to create some invalid configurations */ ++ /* Non-numeric port */ ++ torture_write_file(LIBSSH_TESTCONFIG11, ++ "Host bad-port\n" ++ "\tProxyJump jumpbox:22bad22\n" ++ ""); ++ torture_reset_config(session); ++ ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + +@@ -752,16 +768,6 @@ static void torture_config_proxyjump(void **state) { + ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + +- /* Too many @ in second jump */ +- torture_write_file(LIBSSH_TESTCONFIG11, +- "Host bad-hostname\n" +- "\tProxyJump localhost,user@principal.com@jumpbox:22\n" +- ""); +- torture_reset_config(session); +- ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); +- ret = ssh_config_parse_file(session, LIBSSH_TESTCONFIG11); +- assert_ssh_return_code_equal(session, ret, SSH_ERROR); +- + /* Braces mismatch in second jump */ + torture_write_file(LIBSSH_TESTCONFIG11, + "Host mismatch\n" +-- +2.41.0 + + +From c3234e5f94b96d6e29f0c1c82821c1e3ebb181ed Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Wed, 1 Nov 2023 11:24:43 +0100 +Subject: [PATCH 02/12] CVE-2023-6004: config_parser: Allow multiple '@' in + usernames + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/config_parser.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/config_parser.c b/src/config_parser.c +index ae2aa2c8..76cca224 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -152,7 +152,7 @@ int ssh_config_parse_uri(const char *tok, + } + + /* Username part (optional) */ +- endp = strchr(tok, '@'); ++ endp = strrchr(tok, '@'); + if (endp != NULL) { + /* Zero-length username is not valid */ + if (tok == endp) { +-- +2.41.0 + + +From a5b8bd0d8841296cf71d927824d60f576581243f Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 31 Oct 2023 09:48:52 +0100 +Subject: [PATCH 03/12] CVE-2023-6004: options: Simplify the hostname parsing + in ssh_options_set + +Using ssh_config_parse_uri can simplify the parsing of the host +parsing inside the function of ssh_options_set + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/options.c | 40 ++++++++++++++++------------------------ + 1 file changed, 16 insertions(+), 24 deletions(-) + +diff --git a/src/options.c b/src/options.c +index b5f951ac..7c03e7ab 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -36,6 +36,7 @@ + #include "libssh/session.h" + #include "libssh/misc.h" + #include "libssh/options.h" ++#include "libssh/config_parser.h" + #ifdef WITH_SERVER + #include "libssh/server.h" + #include "libssh/bind.h" +@@ -490,33 +491,24 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + ssh_set_error_invalid(session); + return -1; + } else { +- q = strdup(value); +- if (q == NULL) { +- ssh_set_error_oom(session); ++ char *username = NULL, *hostname = NULL, *port = NULL; ++ rc = ssh_config_parse_uri(value, &username, &hostname, &port); ++ if (rc != SSH_OK) { + return -1; + } +- p = strchr(q, '@'); +- +- SAFE_FREE(session->opts.host); +- +- if (p) { +- *p = '\0'; +- session->opts.host = strdup(p + 1); +- if (session->opts.host == NULL) { +- SAFE_FREE(q); +- ssh_set_error_oom(session); +- return -1; +- } +- ++ if (port != NULL) { ++ SAFE_FREE(username); ++ SAFE_FREE(hostname); ++ SAFE_FREE(port); ++ return -1; ++ } ++ if (username != NULL) { + SAFE_FREE(session->opts.username); +- session->opts.username = strdup(q); +- SAFE_FREE(q); +- if (session->opts.username == NULL) { +- ssh_set_error_oom(session); +- return -1; +- } +- } else { +- session->opts.host = q; ++ session->opts.username = username; ++ } ++ if (hostname != NULL) { ++ SAFE_FREE(session->opts.host); ++ session->opts.host = hostname; + } + } + break; +-- +2.41.0 + + +From efb24b6472e8b87c5832c0590f14e99e82fcdeeb Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 12:44:16 +0200 +Subject: [PATCH 04/12] CVE-2023-6004: misc: Add function to check allowed + characters of a hostname + +The hostname can be a domain name or an ip address. The colon has to be +allowed because of IPv6 even it is prohibited in domain names. + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + include/libssh/misc.h | 2 ++ + src/misc.c | 68 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 70 insertions(+) + +diff --git a/include/libssh/misc.h b/include/libssh/misc.h +index 3cc3b113..a5bee930 100644 +--- a/include/libssh/misc.h ++++ b/include/libssh/misc.h +@@ -97,4 +97,6 @@ int ssh_mkdirs(const char *pathname, mode_t mode); + int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); + int ssh_newline_vis(const char *string, char *buf, size_t buf_len); + ++int ssh_check_hostname_syntax(const char *hostname); ++ + #endif /* MISC_H_ */ +diff --git a/src/misc.c b/src/misc.c +index 149eb85e..e4239e81 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -94,6 +94,8 @@ + #define ZLIB_STRING "" + #endif + ++#define ARPA_DOMAIN_MAX_LEN 63 ++ + /** + * @defgroup libssh_misc The SSH helper functions. + * @ingroup libssh +@@ -1734,4 +1736,70 @@ int ssh_newline_vis(const char *string, char *buf, size_t buf_len) + return out - buf; + } + ++/** ++ * @brief Checks syntax of a domain name ++ * ++ * The check is made based on the RFC1035 section 2.3.1 ++ * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) ++ * ++ * The label should be no longer than 63 characters ++ * The label should start with a letter and end with a letter or number ++ * The label in this implementation can start with a number to allow virtual ++ * URLs to pass. Note that this will make IPv4 addresses to pass ++ * this check too. ++ * ++ * @param hostname The domain name to be checked, has to be null terminated ++ * ++ * @return SSH_OK if the hostname passes syntax check ++ * SSH_ERROR otherwise or if hostname is NULL or empty string ++ */ ++int ssh_check_hostname_syntax(const char *hostname) ++{ ++ char *it = NULL, *s = NULL, *buf = NULL; ++ size_t it_len; ++ char c; ++ ++ if (hostname == NULL || strlen(hostname) == 0) { ++ return SSH_ERROR; ++ } ++ ++ /* strtok_r writes into the string, keep the input clean */ ++ s = strdup(hostname); ++ if (s == NULL) { ++ return SSH_ERROR; ++ } ++ ++ it = strtok_r(s, ".", &buf); ++ /* if the token has 0 length */ ++ if (it == NULL) { ++ free(s); ++ return SSH_ERROR; ++ } ++ do { ++ it_len = strlen(it); ++ if (it_len > ARPA_DOMAIN_MAX_LEN || ++ /* the first char must be a letter, but some virtual urls start ++ * with a number */ ++ isalnum(it[0]) == 0 || ++ isalnum(it[it_len - 1]) == 0) { ++ free(s); ++ return SSH_ERROR; ++ } ++ while (*it != '\0') { ++ c = *it; ++ /* the "." is allowed too, but tokenization removes it from the ++ * string */ ++ if (isalnum(c) == 0 && c != '-') { ++ free(s); ++ return SSH_ERROR; ++ } ++ it++; ++ } ++ } while ((it = strtok_r(NULL, ".", &buf)) != NULL); ++ ++ free(s); ++ ++ return SSH_OK; ++} ++ + /** @} */ +-- +2.41.0 + + +From 234ecdf4d9705efa3727a54dcc1ddfe6377c7bf6 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 12:45:28 +0200 +Subject: [PATCH 05/12] CVE-2023-6004: torture_misc: Add test for + ssh_check_hostname_syntax + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 73 ++++++++++++++++++++++++++++++++++ + 1 file changed, 73 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index 0a48abbe..d14f4254 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -656,6 +656,78 @@ static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) + assert_string_equal(buffer, "a\\nb\\n"); + } + ++static void torture_ssh_check_hostname_syntax(void **state) ++{ ++ int rc; ++ (void)state; ++ ++ rc = ssh_check_hostname_syntax("duckduckgo.com"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("www.libssh.org"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("Some-Thing.com"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("localhost"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("a"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("a-0.b-b"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_hostname_syntax("libssh."); ++ assert_int_equal(rc, SSH_OK); ++ ++ rc = ssh_check_hostname_syntax(NULL); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(""); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("/"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("@"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("["); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("`"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("{"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("&"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("|"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("\""); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("`"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(" "); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("!www.libssh.org"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("--.--"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("libssh-"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax("."); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_hostname_syntax(".."); ++ assert_int_equal(rc, SSH_ERROR); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -678,6 +750,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_newline_vis), + cmocka_unit_test(torture_ssh_mkdirs), + cmocka_unit_test(torture_ssh_quote_file_name), ++ cmocka_unit_test(torture_ssh_check_hostname_syntax), + }; + + ssh_init(); +-- +2.41.0 + + +From 4d7ae19e9cd8c407012b40f3f2eaf480bfb1da7d Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 18:33:56 +0200 +Subject: [PATCH 06/12] CVE-2023-6004: config_parser: Check for valid syntax of + a hostname if it is a domain name + +This prevents code injection. +The domain name syntax checker is based on RFC1035. + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/config_parser.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/src/config_parser.c b/src/config_parser.c +index 76cca224..87bac5d4 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -30,6 +30,7 @@ + + #include "libssh/config_parser.h" + #include "libssh/priv.h" ++#include "libssh/misc.h" + + char *ssh_config_get_cmd(char **str) + { +@@ -139,6 +140,7 @@ int ssh_config_parse_uri(const char *tok, + { + char *endp = NULL; + long port_n; ++ int rc; + + /* Sanitize inputs */ + if (username != NULL) { +@@ -196,6 +198,14 @@ int ssh_config_parse_uri(const char *tok, + if (*hostname == NULL) { + goto error; + } ++ /* if not an ip, check syntax */ ++ rc = ssh_is_ipaddr(*hostname); ++ if (rc == 0) { ++ rc = ssh_check_hostname_syntax(*hostname); ++ if (rc != SSH_OK) { ++ goto error; ++ } ++ } + } + /* Skip also the closing bracket */ + if (*endp == ']') { +-- +2.41.0 + + +From 8cf4f4bfda968ab526c1a601ea1030bbaccaba17 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 10 Oct 2023 10:28:47 +0200 +Subject: [PATCH 07/12] CVE-2023-6004: torture_proxycommand: Add test for + proxycommand injection + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/client/torture_proxycommand.c | 53 +++++++++++++++++++++++++++++ + 1 file changed, 53 insertions(+) + +diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c +index c04ff2ab..dc17f3d8 100644 +--- a/tests/client/torture_proxycommand.c ++++ b/tests/client/torture_proxycommand.c +@@ -161,6 +161,56 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) + assert_int_equal(rc & O_RDWR, O_RDWR); + } + ++static void torture_options_proxycommand_injection(void **state) ++{ ++ struct torture_state *s = *state; ++ struct passwd *pwd = NULL; ++ const char *malicious_host = "`echo foo > mfile`"; ++ const char *command = "nc %h %p"; ++ char *current_dir = NULL; ++ char *malicious_file_path = NULL; ++ int mfp_len; ++ int verbosity = torture_libssh_verbosity(); ++ struct stat sb; ++ int rc; ++ ++ pwd = getpwnam("bob"); ++ assert_non_null(pwd); ++ ++ rc = setuid(pwd->pw_uid); ++ assert_return_code(rc, errno); ++ ++ s->ssh.session = ssh_new(); ++ assert_non_null(s->ssh.session); ++ ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); ++ // if we would be checking the rc, this should fail ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); ++ ++ ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); ++ ++ rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); ++ assert_int_equal(rc, 0); ++ rc = ssh_connect(s->ssh.session); ++ assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); ++ ++ current_dir = torture_get_current_working_dir(); ++ assert_non_null(current_dir); ++ mfp_len = strlen(current_dir) + 6; ++ malicious_file_path = malloc(mfp_len); ++ assert_non_null(malicious_file_path); ++ rc = snprintf(malicious_file_path, mfp_len, ++ "%s/mfile", current_dir); ++ assert_int_equal(rc, mfp_len); ++ free(current_dir); ++ rc = stat(malicious_file_path, &sb); ++ assert_int_not_equal(rc, 0); ++ ++ // cleanup ++ remove(malicious_file_path); ++ free(malicious_file_path); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -176,6 +226,9 @@ int torture_run_tests(void) { + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, ++ NULL, ++ session_teardown), + }; + + +-- +2.41.0 + + +From a0dbe0d556e073804cc549802569577bb24757d9 Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Mon, 6 Nov 2023 20:11:38 +0100 +Subject: [PATCH 08/12] CVE-2023-6004: torture_misc: Add test for ssh_is_ipaddr + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index d14f4254..073bc54c 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -728,6 +728,31 @@ static void torture_ssh_check_hostname_syntax(void **state) + assert_int_equal(rc, SSH_ERROR); + } + ++static void torture_ssh_is_ipaddr(void **state) { ++ int rc; ++ (void)state; ++ ++ rc = ssh_is_ipaddr("201.255.3.69"); ++ assert_int_equal(rc, 1); ++ rc = ssh_is_ipaddr("::1"); ++ assert_int_equal(rc, 1); ++ rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); ++ assert_int_equal(rc, 1); ++ ++ rc = ssh_is_ipaddr(".."); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr(":::"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("1.1.1.1.1"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("1.1"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("caesar"); ++ assert_int_equal(rc, 0); ++ rc = ssh_is_ipaddr("::xa:1"); ++ assert_int_equal(rc, 0); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -751,6 +776,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_mkdirs), + cmocka_unit_test(torture_ssh_quote_file_name), + cmocka_unit_test(torture_ssh_check_hostname_syntax), ++ cmocka_unit_test(torture_ssh_is_ipaddr), + }; + + ssh_init(); +-- +2.41.0 + + +From cdaec0d6273243a03f460cc5ba1a2265b4afb93a Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 28 Nov 2023 15:26:45 +0100 +Subject: [PATCH 09/12] CVE-2023-6004: misc: Add ipv6 link-local check for an + ip address + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + src/CMakeLists.txt | 17 ++++++++++------- + src/connect.c | 2 +- + src/misc.c | 44 ++++++++++++++++++++++++++++++++++++++------ + 3 files changed, 49 insertions(+), 14 deletions(-) + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index a576cf71..fc401793 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -9,13 +9,6 @@ set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_REQUIRED_LIBRARIES} + ) + +-if (WIN32) +- set(LIBSSH_LINK_LIBRARIES +- ${LIBSSH_LINK_LIBRARIES} +- ws2_32 +- ) +-endif (WIN32) +- + if (OPENSSL_CRYPTO_LIBRARIES) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} +@@ -93,6 +86,16 @@ if (MINGW AND Threads_FOUND) + ) + endif() + ++# This needs to be last for mingw to build ++# https://gitlab.com/libssh/libssh-mirror/-/issues/84 ++if (WIN32) ++ set(LIBSSH_LINK_LIBRARIES ++ ${LIBSSH_LINK_LIBRARIES} ++ iphlpapi ++ ws2_32 ++ ) ++endif (WIN32) ++ + if (BUILD_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static +diff --git a/src/connect.c b/src/connect.c +index ce4d58df..ca62dcf0 100644 +--- a/src/connect.c ++++ b/src/connect.c +@@ -136,7 +136,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) + #endif + } + +- if (ssh_is_ipaddr(host)) { ++ if (ssh_is_ipaddr(host) == 1) { + /* this is an IP address */ + SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); + hints.ai_flags |= AI_NUMERICHOST; +diff --git a/src/misc.c b/src/misc.c +index e4239e81..6f5d2d60 100644 +--- a/src/misc.c ++++ b/src/misc.c +@@ -32,6 +32,7 @@ + #include + #include + #include ++#include + + #endif /* _WIN32 */ + +@@ -59,6 +60,7 @@ + #include + #include + #include ++#include + + #ifdef HAVE_IO_H + #include +@@ -216,22 +218,37 @@ int ssh_is_ipaddr_v4(const char *str) { + + int ssh_is_ipaddr(const char *str) { + int rc = SOCKET_ERROR; ++ char *s = strdup(str); + +- if (strchr(str, ':')) { ++ if (s == NULL) { ++ return -1; ++ } ++ if (strchr(s, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); ++ char *network_interface = strchr(s, '%'); + +- /* TODO link-local (IP:v6:addr%ifname). */ +- rc = WSAStringToAddressA((LPSTR) str, ++ /* link-local (IP:v6:addr%ifname). */ ++ if (network_interface != NULL) { ++ rc = if_nametoindex(network_interface + 1); ++ if (rc == 0) { ++ free(s); ++ return 0; ++ } ++ *network_interface = '\0'; ++ } ++ rc = WSAStringToAddressA((LPSTR) s, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { ++ free(s); + return 1; + } + } + ++ free(s); + return ssh_is_ipaddr_v4(str); + } + #else /* _WIN32 */ +@@ -335,17 +352,32 @@ int ssh_is_ipaddr_v4(const char *str) { + + int ssh_is_ipaddr(const char *str) { + int rc = -1; ++ char *s = strdup(str); + +- if (strchr(str, ':')) { ++ if (s == NULL) { ++ return -1; ++ } ++ if (strchr(s, ':')) { + struct in6_addr dest6; ++ char *network_interface = strchr(s, '%'); + +- /* TODO link-local (IP:v6:addr%ifname). */ +- rc = inet_pton(AF_INET6, str, &dest6); ++ /* link-local (IP:v6:addr%ifname). */ ++ if (network_interface != NULL) { ++ rc = if_nametoindex(network_interface + 1); ++ if (rc == 0) { ++ free(s); ++ return 0; ++ } ++ *network_interface = '\0'; ++ } ++ rc = inet_pton(AF_INET6, s, &dest6); + if (rc > 0) { ++ free(s); + return 1; + } + } + ++ free(s); + return ssh_is_ipaddr_v4(str); + } + +-- +2.41.0 + + +From 6a8a18c73e73a338283dfbade0a7d83e5cfafe3b Mon Sep 17 00:00:00 2001 +From: Norbert Pocs +Date: Tue, 28 Nov 2023 15:27:31 +0100 +Subject: [PATCH 10/12] CVE-2023-6004: torture_misc: Add tests for ipv6 + link-local + +Signed-off-by: Norbert Pocs +Reviewed-by: Andreas Schneider +--- + tests/unittests/torture_misc.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c +index 073bc54c..f16b766e 100644 +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -17,7 +17,14 @@ + #include "misc.c" + #include "error.c" + ++#ifdef _WIN32 ++#include ++#else ++#include ++#endif ++ + #define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." ++#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" + + const char template[] = "temp_dir_XXXXXX"; + +@@ -730,14 +737,27 @@ static void torture_ssh_check_hostname_syntax(void **state) + + static void torture_ssh_is_ipaddr(void **state) { + int rc; ++ char *interf = malloc(64); ++ char *test_interf = malloc(128); + (void)state; + ++ assert_non_null(interf); ++ assert_non_null(test_interf); + rc = ssh_is_ipaddr("201.255.3.69"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("::1"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assert_int_equal(rc, 1); ++ if_indextoname(1, interf); ++ assert_non_null(interf); ++ rc = sprintf(test_interf, TORTURE_IPV6_LOCAL_LINK, interf); ++ /* the "%%s" is not written */ ++ assert_int_equal(rc, strlen(interf) + strlen(TORTURE_IPV6_LOCAL_LINK) - 3); ++ rc = ssh_is_ipaddr(test_interf); ++ assert_int_equal(rc, 1); ++ free(interf); ++ free(test_interf); + + rc = ssh_is_ipaddr(".."); + assert_int_equal(rc, 0); +-- +2.41.0 + + +From 72f59157e6ccbd4c0bb806690931413169a0886f Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 22 Dec 2023 10:32:40 +0100 +Subject: [PATCH 11/12] Fix regression in IPv6 addresses in hostname parsing + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +(cherry picked from commit 4f997aee7c7d7ea346b3e8ba505da0b7601ff318) +--- + include/libssh/config_parser.h | 11 ++++++++--- + src/config.c | 4 ++-- + src/config_parser.c | 16 +++++++++++----- + src/options.c | 10 ++-------- + 4 files changed, 23 insertions(+), 18 deletions(-) + +diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h +index e974917c..ee647bfb 100644 +--- a/include/libssh/config_parser.h ++++ b/include/libssh/config_parser.h +@@ -26,6 +26,8 @@ + #ifndef CONFIG_PARSER_H_ + #define CONFIG_PARSER_H_ + ++#include ++ + char *ssh_config_get_cmd(char **str); + + char *ssh_config_get_token(char **str); +@@ -45,13 +47,16 @@ int ssh_config_get_yesno(char **str, int notfound); + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. ++ * @param[in] ignore_port Set to true if the we should not attempt to parse ++ * port number. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ + int ssh_config_parse_uri(const char *tok, +- char **username, +- char **hostname, +- char **port); ++ char **username, ++ char **hostname, ++ char **port, ++ bool ignore_port); + + #endif /* LIBSSH_CONFIG_H_ */ +diff --git a/src/config.c b/src/config.c +index 54ada276..a813568d 100644 +--- a/src/config.c ++++ b/src/config.c +@@ -324,7 +324,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) + } + if (parse_entry) { + /* We actually care only about the first item */ +- rv = ssh_config_parse_uri(cp, &username, &hostname, &port); ++ rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); + /* The rest of the list needs to be passed on */ + if (endp != NULL) { + next = strdup(endp + 1); +@@ -335,7 +335,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) + } + } else { + /* The rest is just sanity-checked to avoid failures later */ +- rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); ++ rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); + } + if (rv != SSH_OK) { + goto out; +diff --git a/src/config_parser.c b/src/config_parser.c +index 87bac5d4..a2da0a62 100644 +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -134,9 +134,10 @@ int ssh_config_get_yesno(char **str, int notfound) + } + + int ssh_config_parse_uri(const char *tok, +- char **username, +- char **hostname, +- char **port) ++ char **username, ++ char **hostname, ++ char **port, ++ bool ignore_port) + { + char *endp = NULL; + long port_n; +@@ -182,12 +183,17 @@ int ssh_config_parse_uri(const char *tok, + if (endp == NULL) { + goto error; + } +- } else { +- /* Hostnames or aliases expand to the last colon or to the end */ ++ } else if (!ignore_port) { ++ /* Hostnames or aliases expand to the last colon (if port is requested) ++ * or to the end */ + endp = strrchr(tok, ':'); + if (endp == NULL) { + endp = strchr(tok, '\0'); + } ++ } else { ++ /* If no port is requested, expand to the end of line ++ * (to accommodate the IPv6 addresses) */ ++ endp = strchr(tok, '\0'); + } + if (tok == endp) { + /* Zero-length hostnames are not valid */ +diff --git a/src/options.c b/src/options.c +index 7c03e7ab..0890ff2e 100644 +--- a/src/options.c ++++ b/src/options.c +@@ -491,17 +491,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, + ssh_set_error_invalid(session); + return -1; + } else { +- char *username = NULL, *hostname = NULL, *port = NULL; +- rc = ssh_config_parse_uri(value, &username, &hostname, &port); ++ char *username = NULL, *hostname = NULL; ++ rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { + return -1; + } +- if (port != NULL) { +- SAFE_FREE(username); +- SAFE_FREE(hostname); +- SAFE_FREE(port); +- return -1; +- } + if (username != NULL) { + SAFE_FREE(session->opts.username); + session->opts.username = username; +-- +2.41.0 + + +From 5dc10ff63ca2e8db91abfdccf1d095f5b4261b8e Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 22 Dec 2023 09:52:18 +0100 +Subject: [PATCH 12/12] tests: Increase test coverage for IPv6 address parsing + as hostnames + +This was an issue in cockpit: + +https://github.com/cockpit-project/cockpit/issues/19772 + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +(cherry picked from commit 6f6e453d7b0ad4ee6a6f6a1c96a9a6b27821410d) +--- + tests/unittests/torture_config.c | 49 +++++++++++++++++++++++++++++++ + tests/unittests/torture_options.c | 22 ++++++++++++++ + 2 files changed, 71 insertions(+) + +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c +index 3a5a74bf..d8097e79 100644 +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -2,6 +2,7 @@ + + #define LIBSSH_STATIC + ++#include + #include "torture.h" + #include "libssh/options.h" + #include "libssh/session.h" +@@ -997,6 +998,53 @@ static void torture_config_match_pattern(void **state) + + } + ++static void torture_config_parse_uri(void **state) ++{ ++ char *username = NULL; ++ char *hostname = NULL; ++ char *port = NULL; ++ int rc; ++ ++ (void)state; /* unused */ ++ ++ rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "localhost"); ++ SAFE_FREE(hostname); ++ assert_null(port); ++ ++ rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1.2.3.4"); ++ SAFE_FREE(hostname); ++ assert_null(port); ++ ++ rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1.2.3.4"); ++ SAFE_FREE(hostname); ++ assert_string_equal(port, "2222"); ++ SAFE_FREE(port); ++ ++ rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1:2:3::4"); ++ SAFE_FREE(hostname); ++ assert_string_equal(port, "2222"); ++ SAFE_FREE(port); ++ ++ /* do not want port */ ++ rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); ++ assert_return_code(rc, errno); ++ assert_null(username); ++ assert_string_equal(hostname, "1:2:3::4"); ++ SAFE_FREE(hostname); ++} ++ + + int torture_run_tests(void) { + int rc; +@@ -1012,6 +1060,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_config_rekey), + cmocka_unit_test(torture_config_pubkeyacceptedkeytypes), + cmocka_unit_test(torture_config_match_pattern), ++ cmocka_unit_test(torture_config_parse_uri), + }; + + +diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c +index d0fdaed1..576ca9cd 100644 +--- a/tests/unittests/torture_options.c ++++ b/tests/unittests/torture_options.c +@@ -59,12 +59,34 @@ static void torture_options_set_host(void **state) { + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "localhost"); + ++ /* IPv4 address */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "127.1.1.1"); ++ assert_null(session->opts.username); ++ ++ /* IPv6 address */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "::1"); ++ assert_null(session->opts.username); ++ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "meditation"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "guru"); ++ ++ /* more @ in uri is OK -- it should go to the username */ ++ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); ++ assert_true(rc == 0); ++ assert_non_null(session->opts.host); ++ assert_string_equal(session->opts.host, "hostname"); ++ assert_non_null(session->opts.username); ++ assert_string_equal(session->opts.username, "at@login"); + } + + static void torture_options_set_ciphers(void **state) { +-- +2.41.0 + diff --git a/SOURCES/CVE-2023-6918.patch b/SOURCES/CVE-2023-6918.patch new file mode 100644 index 0000000..a5bd07d --- /dev/null +++ b/SOURCES/CVE-2023-6918.patch @@ -0,0 +1,1577 @@ +From 93c1dbd69f07f324c6aa1ab9296a632489cd3ead Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 10:30:09 +0100 +Subject: [PATCH 1/5] CVE-2023-6918: kdf: Reformat + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + src/kdf.c | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +diff --git a/src/kdf.c b/src/kdf.c +index 09644739..656a38ed 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -58,7 +58,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + } + + ctx->digest_type = type; +- switch(type){ ++ switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + return ctx; +@@ -79,7 +79,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + + static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) + { +- switch(ctx->digest_type){ ++ switch (ctx->digest_type) { + case SSH_KDF_SHA1: + sha1_update(ctx->ctx.sha1_ctx, data, len); + break; +@@ -97,26 +97,28 @@ static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) + + static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) + { +- switch(ctx->digest_type){ ++ switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_final(md,ctx->ctx.sha1_ctx); ++ sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_final(md,ctx->ctx.sha256_ctx); ++ sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_final(md,ctx->ctx.sha384_ctx); ++ sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_final(md,ctx->ctx.sha512_ctx); ++ sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); + } + + int sshkdf_derive_key(struct ssh_crypto_struct *crypto, +- unsigned char *key, size_t key_len, +- int key_type, unsigned char *output, ++ unsigned char *key, ++ size_t key_len, ++ int key_type, ++ unsigned char *output, + size_t requested_len) + { + /* Can't use VLAs with Visual Studio, so allocate the biggest +-- +2.41.0 + + +From 882d9cb5c8d37d93f9b349d517e59bf496817007 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 12:55:27 +0100 +Subject: [PATCH 2/5] CVE-2023-6918: Remove unused evp functions and types + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/libcrypto.h | 5 --- + include/libssh/libgcrypt.h | 1 - + include/libssh/libmbedcrypto.h | 1 - + include/libssh/wrapper.h | 5 --- + src/libcrypto.c | 55 +------------------------ + src/libgcrypt.c | 52 ------------------------ + src/libmbedcrypto.c | 74 ---------------------------------- + 7 files changed, 1 insertion(+), 192 deletions(-) + +diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h +index 4117942c..35b277c5 100644 +--- a/include/libssh/libcrypto.h ++++ b/include/libssh/libcrypto.h +@@ -39,11 +39,6 @@ typedef EVP_MD_CTX* SHA384CTX; + typedef EVP_MD_CTX* SHA512CTX; + typedef EVP_MD_CTX* MD5CTX; + typedef HMAC_CTX* HMACCTX; +-#ifdef HAVE_ECC +-typedef EVP_MD_CTX *EVPCTX; +-#else +-typedef void *EVPCTX; +-#endif + + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH + #define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h +index 347d851b..3a803fa4 100644 +--- a/include/libssh/libgcrypt.h ++++ b/include/libssh/libgcrypt.h +@@ -32,7 +32,6 @@ typedef gcry_md_hd_t SHA384CTX; + typedef gcry_md_hd_t SHA512CTX; + typedef gcry_md_hd_t MD5CTX; + typedef gcry_md_hd_t HMACCTX; +-typedef gcry_md_hd_t EVPCTX; + #define SHA_DIGEST_LENGTH 20 + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH + #define MD5_DIGEST_LEN 16 +diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h +index fe53019b..b6e3e2a3 100644 +--- a/include/libssh/libmbedcrypto.h ++++ b/include/libssh/libmbedcrypto.h +@@ -41,7 +41,6 @@ typedef mbedtls_md_context_t *SHA384CTX; + typedef mbedtls_md_context_t *SHA512CTX; + typedef mbedtls_md_context_t *MD5CTX; + typedef mbedtls_md_context_t *HMACCTX; +-typedef mbedtls_md_context_t *EVPCTX; + + #define SHA_DIGEST_LENGTH 20 + #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h +index ba64939b..2f5ce189 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -90,11 +90,6 @@ void sha512_update(SHA512CTX c, const void *data, unsigned long len); + void sha512_final(unsigned char *md,SHA512CTX c); + void sha512(const unsigned char *digest, int len, unsigned char *hash); + +-void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen); +-EVPCTX evp_init(int nid); +-void evp_update(EVPCTX ctx, const void *data, unsigned long len); +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); +- + HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); + void hmac_update(HMACCTX c, const void *data, unsigned long len); + void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 3db75df6..5f3917ba 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -148,60 +148,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) + } + } + +-#ifdef HAVE_OPENSSL_ECC +-static const EVP_MD *nid_to_evpmd(int nid) +-{ +- switch (nid) { +- case NID_X9_62_prime256v1: +- return EVP_sha256(); +- case NID_secp384r1: +- return EVP_sha384(); +- case NID_secp521r1: +- return EVP_sha512(); +- default: +- return NULL; +- } +- +- return NULL; +-} +- +-void evp(int nid, unsigned char *digest, int len, unsigned char *hash, unsigned int *hlen) +-{ +- const EVP_MD *evp_md = nid_to_evpmd(nid); +- EVP_MD_CTX *md = EVP_MD_CTX_new(); +- +- EVP_DigestInit(md, evp_md); +- EVP_DigestUpdate(md, digest, len); +- EVP_DigestFinal(md, hash, hlen); +- EVP_MD_CTX_free(md); +-} +- +-EVPCTX evp_init(int nid) +-{ +- const EVP_MD *evp_md = nid_to_evpmd(nid); +- +- EVPCTX ctx = EVP_MD_CTX_new(); +- if (ctx == NULL) { +- return NULL; +- } +- +- EVP_DigestInit(ctx, evp_md); +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- EVP_DigestUpdate(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- EVP_DigestFinal(ctx, md, mdlen); +- EVP_MD_CTX_free(ctx); +-} +-#endif +- + SHA256CTX sha256_init(void) + { + int rc; +@@ -345,6 +291,7 @@ void md5_final(unsigned char *md, MD5CTX c) + EVP_MD_CTX_destroy(c); + } + ++ + #ifdef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID + static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) + { +diff --git a/src/libgcrypt.c b/src/libgcrypt.c +index 8fbf2157..49488793 100644 +--- a/src/libgcrypt.c ++++ b/src/libgcrypt.c +@@ -82,58 +82,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + } + +-#ifdef HAVE_GCRYPT_ECC +-static int nid_to_md_algo(int nid) +-{ +- switch (nid) { +- case NID_gcrypt_nistp256: +- return GCRY_MD_SHA256; +- case NID_gcrypt_nistp384: +- return GCRY_MD_SHA384; +- case NID_gcrypt_nistp521: +- return GCRY_MD_SHA512; +- } +- return GCRY_MD_NONE; +-} +- +-void evp(int nid, unsigned char *digest, int len, +- unsigned char *hash, unsigned int *hlen) +-{ +- int algo = nid_to_md_algo(nid); +- +- /* Note: What gcrypt calls 'hash' is called 'digest' here and +- vice-versa. */ +- gcry_md_hash_buffer(algo, hash, digest, len); +- *hlen = gcry_md_get_algo_dlen(algo); +-} +- +-EVPCTX evp_init(int nid) +-{ +- gcry_error_t err; +- int algo = nid_to_md_algo(nid); +- EVPCTX ctx; +- +- err = gcry_md_open(&ctx, algo, 0); +- if (err) { +- return NULL; +- } +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- gcry_md_write(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- int algo = gcry_md_get_algo(ctx); +- *mdlen = gcry_md_get_algo_dlen(algo); +- memcpy(md, gcry_md_read(ctx, algo), *mdlen); +- gcry_md_close(ctx); +-} +-#endif + + SHA256CTX sha256_init(void) { + SHA256CTX ctx = NULL; +diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c +index a2e74d3b..f37a6a6d 100644 +--- a/src/libmbedcrypto.c ++++ b/src/libmbedcrypto.c +@@ -103,80 +103,6 @@ void sha1(const unsigned char *digest, int len, unsigned char *hash) + } + } + +-static mbedtls_md_type_t nid_to_md_algo(int nid) +-{ +- switch (nid) { +- case NID_mbedtls_nistp256: +- return MBEDTLS_MD_SHA256; +- case NID_mbedtls_nistp384: +- return MBEDTLS_MD_SHA384; +- case NID_mbedtls_nistp521: +- return MBEDTLS_MD_SHA512; +- } +- return MBEDTLS_MD_NONE; +-} +- +-void evp(int nid, unsigned char *digest, int len, +- unsigned char *hash, unsigned int *hlen) +-{ +- mbedtls_md_type_t algo = nid_to_md_algo(nid); +- const mbedtls_md_info_t *md_info = +- mbedtls_md_info_from_type(algo); +- +- +- if (md_info != NULL) { +- *hlen = mbedtls_md_get_size(md_info); +- mbedtls_md(md_info, digest, len, hash); +- } +-} +- +-EVPCTX evp_init(int nid) +-{ +- EVPCTX ctx = NULL; +- int rc; +- mbedtls_md_type_t algo = nid_to_md_algo(nid); +- const mbedtls_md_info_t *md_info = +- mbedtls_md_info_from_type(algo); +- +- if (md_info == NULL) { +- return NULL; +- } +- +- ctx = malloc(sizeof(mbedtls_md_context_t)); +- if (ctx == NULL) { +- return NULL; +- } +- +- mbedtls_md_init(ctx); +- +- rc = mbedtls_md_setup(ctx, md_info, 0); +- if (rc != 0) { +- SAFE_FREE(ctx); +- return NULL; +- } +- +- rc = mbedtls_md_starts(ctx); +- if (rc != 0) { +- SAFE_FREE(ctx); +- return NULL; +- } +- +- return ctx; +-} +- +-void evp_update(EVPCTX ctx, const void *data, unsigned long len) +-{ +- mbedtls_md_update(ctx, data, len); +-} +- +-void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) +-{ +- *mdlen = mbedtls_md_get_size(ctx->md_info); +- mbedtls_md_finish(ctx, md); +- mbedtls_md_free(ctx); +- SAFE_FREE(ctx); +-} +- + SHA256CTX sha256_init(void) + { + SHA256CTX ctx = NULL; +-- +2.41.0 + + +From a45a3c940d17abb3bcd2b924ccd5cd68eb8fd753 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 12:55:54 +0100 +Subject: [PATCH 3/5] CVE-2023-6918: Systematically check return values when + calculating digests + +with all crypto backends + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + include/libssh/wrapper.h | 34 +++++--- + src/kdf.c | 96 +++++++++++++++++---- + src/libcrypto.c | 166 +++++++++++++++++++++++++++-------- + src/libgcrypt.c | 142 +++++++++++++++++++++++------- + src/libmbedcrypto.c | 182 ++++++++++++++++++++++++++++++--------- + src/session.c | 72 ++++++++++++---- + 6 files changed, 533 insertions(+), 159 deletions(-) + +diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h +index 2f5ce189..e6384b50 100644 +--- a/include/libssh/wrapper.h ++++ b/include/libssh/wrapper.h +@@ -67,32 +67,38 @@ struct ssh_crypto_struct; + + typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; + MD5CTX md5_init(void); +-void md5_update(MD5CTX c, const void *data, unsigned long len); +-void md5_final(unsigned char *md,MD5CTX c); ++void md5_ctx_free(MD5CTX); ++int md5_update(MD5CTX c, const void *data, unsigned long len); ++int md5_final(unsigned char *md,MD5CTX c); + + SHACTX sha1_init(void); +-void sha1_update(SHACTX c, const void *data, unsigned long len); +-void sha1_final(unsigned char *md,SHACTX c); +-void sha1(const unsigned char *digest,int len,unsigned char *hash); ++void sha1_ctx_free(SHACTX); ++int sha1_update(SHACTX c, const void *data, unsigned long len); ++int sha1_final(unsigned char *md,SHACTX c); ++int sha1(const unsigned char *digest,int len,unsigned char *hash); + + SHA256CTX sha256_init(void); +-void sha256_update(SHA256CTX c, const void *data, unsigned long len); +-void sha256_final(unsigned char *md,SHA256CTX c); +-void sha256(const unsigned char *digest, int len, unsigned char *hash); ++void sha256_ctx_free(SHA256CTX); ++int sha256_update(SHA256CTX c, const void *data, unsigned long len); ++int sha256_final(unsigned char *md,SHA256CTX c); ++int sha256(const unsigned char *digest, int len, unsigned char *hash); + + SHA384CTX sha384_init(void); +-void sha384_update(SHA384CTX c, const void *data, unsigned long len); +-void sha384_final(unsigned char *md,SHA384CTX c); +-void sha384(const unsigned char *digest, int len, unsigned char *hash); ++void sha384_ctx_free(SHA384CTX); ++int sha384_update(SHA384CTX c, const void *data, unsigned long len); ++int sha384_final(unsigned char *md,SHA384CTX c); ++int sha384(const unsigned char *digest, int len, unsigned char *hash); + + SHA512CTX sha512_init(void); +-void sha512_update(SHA512CTX c, const void *data, unsigned long len); +-void sha512_final(unsigned char *md,SHA512CTX c); +-void sha512(const unsigned char *digest, int len, unsigned char *hash); ++void sha512_ctx_free(SHA512CTX); ++int sha512_update(SHA512CTX c, const void *data, unsigned long len); ++int sha512_final(unsigned char *md,SHA512CTX c); ++int sha512(const unsigned char *digest, int len, unsigned char *hash); + + HMACCTX hmac_init(const void *key,int len, enum ssh_hmac_e type); + void hmac_update(HMACCTX c, const void *data, unsigned long len); + void hmac_final(HMACCTX ctx,unsigned char *hashmacbuf,unsigned int *len); ++ + size_t hmac_digest_len(enum ssh_hmac_e type); + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/kdf.c b/src/kdf.c +index 656a38ed..90f6e9f3 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -77,41 +77,64 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + } + } + +-static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) ++static void ssh_mac_ctx_free(ssh_mac_ctx ctx) + { ++ if (ctx == NULL) { ++ return; ++ } ++ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_update(ctx->ctx.sha1_ctx, data, len); ++ sha1_ctx_free(ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_update(ctx->ctx.sha256_ctx, data, len); ++ sha256_ctx_free(ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_update(ctx->ctx.sha384_ctx, data, len); ++ sha384_ctx_free(ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_update(ctx->ctx.sha512_ctx, data, len); ++ sha512_ctx_free(ctx->ctx.sha512_ctx); + break; + } ++ SAFE_FREE(ctx); ++} ++ ++static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) ++{ ++ switch (ctx->digest_type) { ++ case SSH_KDF_SHA1: ++ return sha1_update(ctx->ctx.sha1_ctx, data, len); ++ case SSH_KDF_SHA256: ++ return sha256_update(ctx->ctx.sha256_ctx, data, len); ++ case SSH_KDF_SHA384: ++ return sha384_update(ctx->ctx.sha384_ctx, data, len); ++ case SSH_KDF_SHA512: ++ return sha512_update(ctx->ctx.sha512_ctx, data, len); ++ } ++ return SSH_ERROR; + } + +-static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) ++static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) + { ++ int rc = SSH_ERROR; ++ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: +- sha1_final(md, ctx->ctx.sha1_ctx); ++ rc = sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: +- sha256_final(md, ctx->ctx.sha256_ctx); ++ rc = sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: +- sha384_final(md, ctx->ctx.sha384_ctx); ++ rc = sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: +- sha512_final(md, ctx->ctx.sha512_ctx); ++ rc = sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); ++ return rc; + } + + int sshkdf_derive_key(struct ssh_crypto_struct *crypto, +@@ -127,6 +150,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + size_t output_len = crypto->digest_len; + char letter = key_type; + ssh_mac_ctx ctx; ++ int rc; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; +@@ -137,11 +161,30 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + return -1; + } + +- ssh_mac_update(ctx, key, key_len); +- ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); +- ssh_mac_update(ctx, &letter, 1); +- ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); +- ssh_mac_final(digest, ctx); ++ rc = ssh_mac_update(ctx, key, key_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, &letter, 1); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_final(digest, ctx); ++ if (rc != SSH_OK) { ++ return -1; ++ } + + if (requested_len < output_len) { + output_len = requested_len; +@@ -153,10 +196,25 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + if (ctx == NULL) { + return -1; + } +- ssh_mac_update(ctx, key, key_len); +- ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); +- ssh_mac_update(ctx, output, output_len); +- ssh_mac_final(digest, ctx); ++ rc = ssh_mac_update(ctx, key, key_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_update(ctx, output, output_len); ++ if (rc != SSH_OK) { ++ ssh_mac_ctx_free(ctx); ++ return -1; ++ } ++ rc = ssh_mac_final(digest, ctx); ++ if (rc != SSH_OK) { ++ return -1; ++ } + if (requested_len < output_len + crypto->digest_len) { + memcpy(output + output_len, digest, requested_len - output_len); + } else { +diff --git a/src/libcrypto.c b/src/libcrypto.c +index 5f3917ba..45f45a9e 100644 +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -126,26 +126,46 @@ SHACTX sha1_init(void) + return c; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) ++void sha1_ctx_free(SHACTX c) + { +- EVP_DigestUpdate(c, data, len); ++ EVP_MD_CTX_destroy(c); + } + +-void sha1_final(unsigned char *md, SHACTX c) ++int sha1_update(SHACTX c, const void *data, unsigned long len) ++{ ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha1_final(unsigned char *md, SHACTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) ++int sha1(const unsigned char *digest, int len, unsigned char *hash) + { + SHACTX c = sha1_init(); +- if (c != NULL) { +- sha1_update(c, digest, len); +- sha1_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha1_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha1_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha1_final(hash, c); + } + + SHA256CTX sha256_init(void) +@@ -164,26 +184,46 @@ SHA256CTX sha256_init(void) + return c; + } + +-void sha256_update(SHA256CTX c, const void *data, unsigned long len) ++void sha256_ctx_free(SHA256CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha256_update(SHA256CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256_final(unsigned char *md, SHA256CTX c) ++int sha256_final(unsigned char *md, SHA256CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash) ++int sha256(const unsigned char *digest, int len, unsigned char *hash) + { + SHA256CTX c = sha256_init(); +- if (c != NULL) { +- sha256_update(c, digest, len); +- sha256_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha256_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha256_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha256_final(hash, c); + } + + SHA384CTX sha384_init(void) +@@ -202,26 +242,47 @@ SHA384CTX sha384_init(void) + return c; + } + +-void sha384_update(SHA384CTX c, const void *data, unsigned long len) ++void ++sha384_ctx_free(SHA384CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha384_update(SHA384CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384_final(unsigned char *md, SHA384CTX c) ++int sha384_final(unsigned char *md, SHA384CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) ++int sha384(const unsigned char *digest, int len, unsigned char *hash) + { + SHA384CTX c = sha384_init(); +- if (c != NULL) { +- sha384_update(c, digest, len); +- sha384_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; + } ++ rc = sha384_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha384_ctx_free(c); ++ return SSH_ERROR; ++ } ++ return sha384_final(hash, c); + } + + SHA512CTX sha512_init(void) +@@ -240,26 +301,46 @@ SHA512CTX sha512_init(void) + return c; + } + +-void sha512_update(SHA512CTX c, const void *data, unsigned long len) ++void sha512_ctx_free(SHA512CTX c) ++{ ++ EVP_MD_CTX_destroy(c); ++} ++ ++int sha512_update(SHA512CTX c, const void *data, unsigned long len) + { +- EVP_DigestUpdate(c, data, len); ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512_final(unsigned char *md, SHA512CTX c) ++int sha512_final(unsigned char *md, SHA512CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) ++int sha512(const unsigned char *digest, int len, unsigned char *hash) + { + SHA512CTX c = sha512_init(); +- if (c != NULL) { +- sha512_update(c, digest, len); +- sha512_final(hash, c); ++ int rc; ++ ++ if (c == NULL) { ++ return SSH_ERROR; ++ } ++ rc = sha512_update(c, digest, len); ++ if (rc != SSH_OK) { ++ sha512_ctx_free(c); ++ return SSH_ERROR; + } ++ return sha512_final(hash, c); + } + + MD5CTX md5_init(void) +@@ -278,17 +359,30 @@ MD5CTX md5_init(void) + return c; + } + +-void md5_update(MD5CTX c, const void *data, unsigned long len) ++void md5_ctx_free(MD5CTX c) + { +- EVP_DigestUpdate(c, data, len); ++ EVP_MD_CTX_destroy(c); + } + +-void md5_final(unsigned char *md, MD5CTX c) ++int md5_update(MD5CTX c, const void *data, unsigned long len) ++{ ++ int rc = EVP_DigestUpdate(c, data, len); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int md5_final(unsigned char *md, MD5CTX c) + { + unsigned int mdlen = 0; ++ int rc = EVP_DigestFinal(c, md, &mdlen); + +- EVP_DigestFinal(c, md, &mdlen); + EVP_MD_CTX_destroy(c); ++ if (rc != 1) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + +diff --git a/src/libgcrypt.c b/src/libgcrypt.c +index 49488793..d1332af3 100644 +--- a/src/libgcrypt.c ++++ b/src/libgcrypt.c +@@ -68,18 +68,35 @@ SHACTX sha1_init(void) { + return ctx; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha1_ctx_free(SHACTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha1_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha1_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); +- gcry_md_close(c); ++int sha1_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) { ++int sha1(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); ++ return SSH_OK; + } + + +@@ -90,18 +107,35 @@ SHA256CTX sha256_init(void) { + return ctx; + } + +-void sha256_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha256_ctx_free(SHA256CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha256_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha256_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); +- gcry_md_close(c); ++int sha256_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA256_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash){ ++int sha256(const unsigned char *digest, int len, unsigned char *hash){ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); ++ return SSH_OK; + } + + SHA384CTX sha384_init(void) { +@@ -111,18 +145,35 @@ SHA384CTX sha384_init(void) { + return ctx; + } + +-void sha384_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha384_ctx_free(SHA384CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha384_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha384_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); +- gcry_md_close(c); ++int sha384_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA384_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) { ++int sha384(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); ++ return SSH_OK; + } + + SHA512CTX sha512_init(void) { +@@ -132,18 +183,35 @@ SHA512CTX sha512_init(void) { + return ctx; + } + +-void sha512_update(SHACTX c, const void *data, unsigned long len) { ++void ++sha512_ctx_free(SHA512CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int sha512_update(SHACTX c, const void *data, unsigned long len) { + gcry_md_write(c, data, len); ++ return SSH_OK; + } + +-void sha512_final(unsigned char *md, SHACTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); +- gcry_md_close(c); ++int sha512_final(unsigned char *md, SHACTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, SHA512_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) { ++int sha512(const unsigned char *digest, int len, unsigned char *hash) { + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); ++ return SSH_OK; + } + + MD5CTX md5_init(void) { +@@ -153,14 +221,30 @@ MD5CTX md5_init(void) { + return c; + } + +-void md5_update(MD5CTX c, const void *data, unsigned long len) { ++void ++md5_ctx_free(MD5CTX c) ++{ ++ gcry_md_close(c); ++} ++ ++int md5_update(MD5CTX c, const void *data, unsigned long len) { + gcry_md_write(c,data,len); ++ return SSH_OK; + } + +-void md5_final(unsigned char *md, MD5CTX c) { +- gcry_md_final(c); +- memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); +- gcry_md_close(c); ++int md5_final(unsigned char *md, MD5CTX c) ++{ ++ unsigned char *tmp = NULL; ++ ++ gcry_md_final(c); ++ tmp = gcry_md_read(c, 0); ++ if (tmp == NULL) { ++ gcry_md_close(c); ++ return SSH_ERROR; ++ } ++ memcpy(md, tmp, MD5_DIGEST_LEN); ++ gcry_md_close(c); ++ return SSH_OK; + } + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c +index f37a6a6d..e9a2b8e5 100644 +--- a/src/libmbedcrypto.c ++++ b/src/libmbedcrypto.c +@@ -82,25 +82,46 @@ SHACTX sha1_init(void) + return ctx; + } + +-void sha1_update(SHACTX c, const void *data, unsigned long len) ++void ++sha1_ctx_free(SHACTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha1_final(unsigned char *md, SHACTX c) ++int sha1_update(SHACTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha1(const unsigned char *digest, int len, unsigned char *hash) ++int sha1_final(unsigned char *md, SHACTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha1_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha1(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; + } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + SHA256CTX sha256_init(void) +@@ -136,25 +157,46 @@ SHA256CTX sha256_init(void) + return ctx; + } + +-void sha256_update(SHA256CTX c, const void *data, unsigned long len) ++void ++sha256_ctx_free(SHA256CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha256_final(unsigned char *md, SHA256CTX c) ++int sha256_update(SHA256CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha256_final(unsigned char *md, SHA256CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha256_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha256(const unsigned char *digest, int len, unsigned char *hash) ++int sha256(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; ++ } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; + } ++ return SSH_OK; + } + + SHA384CTX sha384_init(void) +@@ -190,25 +232,46 @@ SHA384CTX sha384_init(void) + return ctx; + } + +-void sha384_update(SHA384CTX c, const void *data, unsigned long len) ++void ++sha384_ctx_free(SHA384CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha384_final(unsigned char *md, SHA384CTX c) ++int sha384_update(SHA384CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha384(const unsigned char *digest, int len, unsigned char *hash) ++int sha384_final(unsigned char *md, SHA384CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha384_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha384(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; ++ } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; + } ++ return SSH_OK; + } + + SHA512CTX sha512_init(void) +@@ -243,25 +306,46 @@ SHA512CTX sha512_init(void) + return ctx; + } + +-void sha512_update(SHA512CTX c, const void *data, unsigned long len) ++void ++sha512_ctx_free(SHA512CTX c) + { +- mbedtls_md_update(c, data, len); ++ mbedtls_md_free(c); ++ SAFE_FREE(c); + } + +-void sha512_final(unsigned char *md, SHA512CTX c) ++int sha512_update(SHA512CTX c, const void *data, unsigned long len) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void sha512(const unsigned char *digest, int len, unsigned char *hash) ++int sha512_final(unsigned char *md, SHA512CTX c) ++{ ++ int rc = mbedtls_md_finish(c, md); ++ sha512_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; ++} ++ ++int sha512(const unsigned char *digest, int len, unsigned char *hash) + { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); +- if (md_info != NULL) { +- mbedtls_md(md_info, digest, len, hash); ++ int rc; ++ ++ if (md_info == NULL) { ++ return SSH_ERROR; + } ++ rc = mbedtls_md(md_info, digest, len, hash); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + MD5CTX md5_init(void) +@@ -296,16 +380,30 @@ MD5CTX md5_init(void) + return ctx; + } + ++void ++md5_ctx_free(MD5CTX c) ++{ ++ mbedtls_md_free(c); ++ SAFE_FREE(c); ++} + +-void md5_update(MD5CTX c, const void *data, unsigned long len) { +- mbedtls_md_update(c, data, len); ++int md5_update(MD5CTX c, const void *data, unsigned long len) ++{ ++ int rc = mbedtls_md_update(c, data, len); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + +-void md5_final(unsigned char *md, MD5CTX c) ++int md5_final(unsigned char *md, MD5CTX c) + { +- mbedtls_md_finish(c, md); +- mbedtls_md_free(c); +- SAFE_FREE(c); ++ int rc = mbedtls_md_finish(c, md); ++ md5_ctx_free(c); ++ if (rc != 0) { ++ return SSH_ERROR; ++ } ++ return SSH_OK; + } + + int ssh_kdf(struct ssh_crypto_struct *crypto, +diff --git a/src/session.c b/src/session.c +index 0cf59e99..349373aa 100644 +--- a/src/session.c ++++ b/src/session.c +@@ -1001,7 +1001,18 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { +- ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); ++ ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); ++ return SSH_ERROR; ++ } ++ ++ rc = ssh_get_server_publickey(session, &pubkey); ++ if (rc != SSH_OK) { ++ return SSH_ERROR; ++ } ++ ++ rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); ++ ssh_key_free(pubkey); ++ if (rc != SSH_OK) { + return SSH_ERROR; + } + +@@ -1016,25 +1027,21 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) + return SSH_ERROR; + } + +- rc = ssh_get_server_publickey(session, &pubkey); ++ rc = md5_update(ctx, ++ ssh_string_data(pubkey_blob), ++ ssh_string_len(pubkey_blob)); + if (rc != SSH_OK) { +- md5_final(h, ctx); ++ md5_ctx_free(ctx); + SAFE_FREE(h); +- return SSH_ERROR; ++ return rc; + } +- +- rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); +- ssh_key_free(pubkey); ++ SSH_STRING_FREE(pubkey_blob); ++ rc = md5_final(h, ctx); + if (rc != SSH_OK) { +- md5_final(h, ctx); + SAFE_FREE(h); +- return SSH_ERROR; ++ return rc; + } + +- md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); +- SSH_STRING_FREE(pubkey_blob); +- md5_final(h, ctx); +- + *hash = h; + + return MD5_DIGEST_LEN; +@@ -1153,8 +1160,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- sha1_final(h, ctx); ++ rc = sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ sha1_ctx_free(ctx); ++ goto out; ++ } ++ rc = sha1_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = SHA_DIGEST_LEN; + } +@@ -1176,8 +1192,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- sha256_final(h, ctx); ++ rc = sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ sha256_ctx_free(ctx); ++ goto out; ++ } ++ rc = sha256_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = SHA256_DIGEST_LEN; + } +@@ -1207,8 +1232,17 @@ int ssh_get_publickey_hash(const ssh_key key, + goto out; + } + +- md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); +- md5_final(h, ctx); ++ rc = md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ if (rc != SSH_OK) { ++ free(h); ++ md5_ctx_free(ctx); ++ goto out; ++ } ++ rc = md5_final(h, ctx); ++ if (rc != SSH_OK) { ++ free(h); ++ goto out; ++ } + + *hlen = MD5_DIGEST_LEN; + } +-- +2.41.0 + + +From 9276027c687723886e8277b77061464303845831 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 13:35:14 +0100 +Subject: [PATCH 4/5] CVE-2023-6918: kdf: Detect context init failures + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + src/kdf.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/src/kdf.c b/src/kdf.c +index 90f6e9f3..b08f0b2f 100644 +--- a/src/kdf.c ++++ b/src/kdf.c +@@ -61,20 +61,32 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) + switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); ++ if (ctx->ctx.sha1_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); ++ if (ctx->ctx.sha256_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA384: + ctx->ctx.sha384_ctx = sha384_init(); ++ if (ctx->ctx.sha384_ctx == NULL) { ++ goto err; ++ } + return ctx; + case SSH_KDF_SHA512: + ctx->ctx.sha512_ctx = sha512_init(); ++ if (ctx->ctx.sha512_ctx == NULL) { ++ goto err; ++ } + return ctx; +- default: +- SAFE_FREE(ctx); +- return NULL; + } ++err: ++ SAFE_FREE(ctx); ++ return NULL; + } + + static void ssh_mac_ctx_free(ssh_mac_ctx ctx) +-- +2.41.0 + + +From aef4a470003bdede61fa17c22418d25ccedaa983 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen +Date: Fri, 15 Dec 2023 15:39:12 +0100 +Subject: [PATCH 5/5] CVE-2023-6918: tests: Code coverage for + ssh_get_pubkey_hash() + +Signed-off-by: Jakub Jelen +Reviewed-by: Andreas Schneider +--- + tests/client/torture_session.c | 35 ++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c +index b5ed7a62..abe458d0 100644 +--- a/tests/client/torture_session.c ++++ b/tests/client/torture_session.c +@@ -118,12 +118,47 @@ static void torture_channel_read_error(void **state) { + ssh_channel_free(channel); + } + ++static void torture_pubkey_hash(void **state) ++{ ++ struct torture_state *s = *state; ++ ssh_session session = s->ssh.session; ++ char *hash = NULL; ++ char *hexa = NULL; ++ int rc = 0; ++ ++ /* bad arguments */ ++ rc = ssh_get_pubkey_hash(session, NULL); ++ assert_int_equal(rc, SSH_ERROR); ++ ++ rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash); ++ assert_int_equal(rc, SSH_ERROR); ++ ++ /* deprecated, but should be covered by tests! */ ++ rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash); ++ if (ssh_fips_mode()) { ++ /* When in FIPS mode, expect the call to fail */ ++ assert_int_equal(rc, SSH_ERROR); ++ } else { ++ assert_int_equal(rc, MD5_DIGEST_LEN); ++ ++ hexa = ssh_get_hexa((unsigned char *)hash, rc); ++ SSH_STRING_FREE_CHAR(hash); ++ assert_string_equal(hexa, ++ "ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b"); ++ ++ SSH_STRING_FREE_CHAR(hexa); ++ } ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_channel_read_error, + session_setup, + session_teardown), ++ cmocka_unit_test_setup_teardown(torture_pubkey_hash, ++ session_setup, ++ session_teardown), + }; + + ssh_init(); +-- +2.41.0 + diff --git a/SPECS/libssh.spec b/SPECS/libssh.spec index 3224980..e239015 100644 --- a/SPECS/libssh.spec +++ b/SPECS/libssh.spec @@ -1,6 +1,6 @@ Name: libssh Version: 0.9.6 -Release: 13%{?dist} +Release: 14%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -18,6 +18,8 @@ Patch3: auth_bypass.patch Patch4: fix_tests.patch Patch5: covscan23.patch Patch6: CVE-2023-48795.patch +Patch7: CVE-2023-6004.patch +Patch8: CVE-2023-6918.patch BuildRequires: cmake BuildRequires: doxygen @@ -146,10 +148,15 @@ popd %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config %changelog -* Wed Jan 24 2024 Sahana Prasad - 0.9.6-13 -- Fix CVE-2023-48795: Prefix truncation attack - on Binary Packet Protocol (BPP) -- Resolves: RHEL-19311 +* Mon Feb 26 2024 Sahana Prasad - 0.9.6-14 +- Fix CVE-2023-48795 Prefix truncation attack on Binary Packet Protocol (BPP) +- Fix CVE-2023-6918 Missing checks for return values for digests +- Fix CVE-2023-6004 ProxyCommand/ProxyJump features allow injection + of malicious code through hostname +- Note: version is bumped from 12 to 14 directly, as the z-stream + version in 8.9 also has 13. So bumping it to 14, will prevent + upgrade conflicts. +- Resolves:RHEL-19690, RHEL-17244, RHEL-19312 * Mon May 15 2023 Norbert Pocs - 0.9.6-12 - Fix loglevel regression