diff options
| author | Deepak Rathore <deeratho@cisco.com> | 2026-04-06 04:55:35 -0700 |
|---|---|---|
| committer | Anuj Mittal <anuj.mittal@oss.qualcomm.com> | 2026-04-24 21:13:20 +0530 |
| commit | ab0866131de4990e9c19a99061e2ceafc2f8c977 (patch) | |
| tree | c8d7b804aece4324540c7eb8494a0bffdd653cfa | |
| parent | cdfa4084fecaa6bf99b99ad930735ccd53550b48 (diff) | |
| download | meta-openembedded-ab0866131de4990e9c19a99061e2ceafc2f8c977.tar.gz | |
libssh: Fix CVE-2026-0965
Pick the patch [1] as mentioned in [2]
[1] https://git.libssh.org/projects/libssh.git/commit/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76
[2] https://security-tracker.debian.org/tracker/CVE-2026-0965
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
| -rw-r--r-- | meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch | 286 | ||||
| -rw-r--r-- | meta-oe/recipes-support/libssh/libssh_0.11.3.bb | 1 |
2 files changed, 287 insertions, 0 deletions
diff --git a/meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch b/meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch new file mode 100644 index 0000000000..57cb9d6170 --- /dev/null +++ b/meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch | |||
| @@ -0,0 +1,286 @@ | |||
| 1 | From 5858a988942d2e25985b34b8c40ce2792cbbe853 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Jakub Jelen <jjelen@redhat.com> | ||
| 3 | Date: Thu, 11 Dec 2025 17:33:19 +0100 | ||
| 4 | Subject: [PATCH 4/4] CVE-2026-0965 config: Do not attempt to read non-regular | ||
| 5 | and too large configuration files | ||
| 6 | |||
| 7 | Changes also the reading of known_hosts to use the new helper function | ||
| 8 | |||
| 9 | CVE: CVE-2026-0965 | ||
| 10 | Upstream-Status: Backport [https://git.libssh.org/projects/libssh.git/commit/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76] | ||
| 11 | |||
| 12 | Signed-off-by: Jakub Jelen <jjelen@redhat.com> | ||
| 13 | Reviewed-by: Andreas Schneider <asn@cryptomilk.org> | ||
| 14 | (cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798) | ||
| 15 | (cherry picked from commit bf390a042623e02abc8f421c4c5fadc0429a8a76) | ||
| 16 | Signed-off-by: Deepak Rathore <deeratho@cisco.com> | ||
| 17 | --- | ||
| 18 | include/libssh/misc.h | 3 ++ | ||
| 19 | include/libssh/priv.h | 3 ++ | ||
| 20 | src/bind_config.c | 4 +- | ||
| 21 | src/config.c | 8 ++-- | ||
| 22 | src/dh-gex.c | 4 +- | ||
| 23 | src/known_hosts.c | 2 +- | ||
| 24 | src/knownhosts.c | 2 +- | ||
| 25 | src/misc.c | 74 ++++++++++++++++++++++++++++++++ | ||
| 26 | tests/unittests/torture_config.c | 20 +++++++++ | ||
| 27 | 9 files changed, 110 insertions(+), 10 deletions(-) | ||
| 28 | |||
| 29 | diff --git a/include/libssh/misc.h b/include/libssh/misc.h | ||
| 30 | index ab726a0e..8eab94ee 100644 | ||
| 31 | --- a/include/libssh/misc.h | ||
| 32 | +++ b/include/libssh/misc.h | ||
| 33 | @@ -36,6 +36,7 @@ | ||
| 34 | #include <sys/types.h> | ||
| 35 | #include <stdbool.h> | ||
| 36 | #endif /* _WIN32 */ | ||
| 37 | +#include <stdio.h> | ||
| 38 | |||
| 39 | #ifdef __cplusplus | ||
| 40 | extern "C" { | ||
| 41 | @@ -136,6 +137,8 @@ int ssh_check_username_syntax(const char *username); | ||
| 42 | void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); | ||
| 43 | bool ssh_libssh_proxy_jumps(void); | ||
| 44 | |||
| 45 | +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); | ||
| 46 | + | ||
| 47 | #ifdef __cplusplus | ||
| 48 | } | ||
| 49 | #endif | ||
| 50 | diff --git a/include/libssh/priv.h b/include/libssh/priv.h | ||
| 51 | index 35fd8506..62069970 100644 | ||
| 52 | --- a/include/libssh/priv.h | ||
| 53 | +++ b/include/libssh/priv.h | ||
| 54 | @@ -473,6 +473,9 @@ char *ssh_strerror(int err_num, char *buf, size_t buflen); | ||
| 55 | #define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) | ||
| 56 | int encode_current_tty_opts(unsigned char *buf, size_t buflen); | ||
| 57 | |||
| 58 | +/** The default maximum file size for a configuration file */ | ||
| 59 | +#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 | ||
| 60 | + | ||
| 61 | #ifdef __cplusplus | ||
| 62 | } | ||
| 63 | #endif | ||
| 64 | diff --git a/src/bind_config.c b/src/bind_config.c | ||
| 65 | index 9e4a7fd4..c12f1003 100644 | ||
| 66 | --- a/src/bind_config.c | ||
| 67 | +++ b/src/bind_config.c | ||
| 68 | @@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind, | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | - f = fopen(filename, "r"); | ||
| 73 | + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 74 | if (f == NULL) { | ||
| 75 | SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", | ||
| 76 | filename); | ||
| 77 | @@ -636,7 +636,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) | ||
| 78 | * option to be redefined later by another file. */ | ||
| 79 | uint8_t seen[BIND_CFG_MAX] = {0}; | ||
| 80 | |||
| 81 | - f = fopen(filename, "r"); | ||
| 82 | + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 83 | if (f == NULL) { | ||
| 84 | return 0; | ||
| 85 | } | ||
| 86 | diff --git a/src/config.c b/src/config.c | ||
| 87 | index b4171efd..1ffad537 100644 | ||
| 88 | --- a/src/config.c | ||
| 89 | +++ b/src/config.c | ||
| 90 | @@ -223,10 +223,9 @@ local_parse_file(ssh_session session, | ||
| 91 | return; | ||
| 92 | } | ||
| 93 | |||
| 94 | - f = fopen(filename, "r"); | ||
| 95 | + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 96 | if (f == NULL) { | ||
| 97 | - SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", | ||
| 98 | - filename); | ||
| 99 | + /* The underlying function logs the reasons */ | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | |||
| 103 | @@ -1466,8 +1465,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename) | ||
| 104 | int parsing, rv; | ||
| 105 | bool global = 0; | ||
| 106 | |||
| 107 | - f = fopen(filename, "r"); | ||
| 108 | + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 109 | if (f == NULL) { | ||
| 110 | + /* The underlying function logs the reasons */ | ||
| 111 | return 0; | ||
| 112 | } | ||
| 113 | |||
| 114 | diff --git a/src/dh-gex.c b/src/dh-gex.c | ||
| 115 | index 46ba934e..428a5655 100644 | ||
| 116 | --- a/src/dh-gex.c | ||
| 117 | +++ b/src/dh-gex.c | ||
| 118 | @@ -519,9 +519,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file, | ||
| 119 | } | ||
| 120 | |||
| 121 | if (moduli_file != NULL) | ||
| 122 | - moduli = fopen(moduli_file, "r"); | ||
| 123 | + moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 124 | else | ||
| 125 | - moduli = fopen(MODULI_FILE, "r"); | ||
| 126 | + moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 127 | |||
| 128 | if (moduli == NULL) { | ||
| 129 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; | ||
| 130 | diff --git a/src/known_hosts.c b/src/known_hosts.c | ||
| 131 | index 3ef83e21..701576ce 100644 | ||
| 132 | --- a/src/known_hosts.c | ||
| 133 | +++ b/src/known_hosts.c | ||
| 134 | @@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, | ||
| 135 | struct ssh_tokens_st *tokens = NULL; | ||
| 136 | |||
| 137 | if (*file == NULL) { | ||
| 138 | - *file = fopen(filename,"r"); | ||
| 139 | + *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 140 | if (*file == NULL) { | ||
| 141 | return NULL; | ||
| 142 | } | ||
| 143 | diff --git a/src/knownhosts.c b/src/knownhosts.c | ||
| 144 | index a2d08a75..3ab468de 100644 | ||
| 145 | --- a/src/knownhosts.c | ||
| 146 | +++ b/src/knownhosts.c | ||
| 147 | @@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries(const char *match, | ||
| 148 | FILE *fp = NULL; | ||
| 149 | int rc; | ||
| 150 | |||
| 151 | - fp = fopen(filename, "r"); | ||
| 152 | + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); | ||
| 153 | if (fp == NULL) { | ||
| 154 | char err_msg[SSH_ERRNO_MSG_MAX] = {0}; | ||
| 155 | SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", | ||
| 156 | diff --git a/src/misc.c b/src/misc.c | ||
| 157 | index 774211fb..3968e6bc 100644 | ||
| 158 | --- a/src/misc.c | ||
| 159 | +++ b/src/misc.c | ||
| 160 | @@ -37,6 +37,7 @@ | ||
| 161 | #endif /* _WIN32 */ | ||
| 162 | |||
| 163 | #include <errno.h> | ||
| 164 | +#include <fcntl.h> | ||
| 165 | #include <limits.h> | ||
| 166 | #include <stdio.h> | ||
| 167 | #include <string.h> | ||
| 168 | @@ -2244,4 +2245,77 @@ ssh_libssh_proxy_jumps(void) | ||
| 169 | return !(t != NULL && t[0] == '1'); | ||
| 170 | } | ||
| 171 | |||
| 172 | +/** | ||
| 173 | + * @internal | ||
| 174 | + * | ||
| 175 | + * @brief Safely open a file containing some configuration. | ||
| 176 | + * | ||
| 177 | + * Runs checks if the file can be used as some configuration file (is regular | ||
| 178 | + * file and is not too large). If so, returns the opened file (for reading). | ||
| 179 | + * Otherwise logs error and returns `NULL`. | ||
| 180 | + * | ||
| 181 | + * @param filename The path to the file to open. | ||
| 182 | + * @param max_file_size Maximum file size that is accepted. | ||
| 183 | + * | ||
| 184 | + * @returns the opened file or `NULL` on error. | ||
| 185 | + */ | ||
| 186 | +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) | ||
| 187 | +{ | ||
| 188 | + FILE *f = NULL; | ||
| 189 | + struct stat sb; | ||
| 190 | + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; | ||
| 191 | + int r, fd; | ||
| 192 | + | ||
| 193 | + /* open first to avoid TOCTOU */ | ||
| 194 | + fd = open(filename, O_RDONLY); | ||
| 195 | + if (fd == -1) { | ||
| 196 | + SSH_LOG(SSH_LOG_RARE, | ||
| 197 | + "Failed to open a file %s for reading: %s", | ||
| 198 | + filename, | ||
| 199 | + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); | ||
| 200 | + return NULL; | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + /* Check the file is sensible for a configuration file */ | ||
| 204 | + r = fstat(fd, &sb); | ||
| 205 | + if (r != 0) { | ||
| 206 | + SSH_LOG(SSH_LOG_RARE, | ||
| 207 | + "Failed to stat %s: %s", | ||
| 208 | + filename, | ||
| 209 | + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); | ||
| 210 | + close(fd); | ||
| 211 | + return NULL; | ||
| 212 | + } | ||
| 213 | + if ((sb.st_mode & S_IFMT) != S_IFREG) { | ||
| 214 | + SSH_LOG(SSH_LOG_RARE, | ||
| 215 | + "The file %s is not a regular file: skipping", | ||
| 216 | + filename); | ||
| 217 | + close(fd); | ||
| 218 | + return NULL; | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + if ((size_t)sb.st_size > max_file_size) { | ||
| 222 | + SSH_LOG(SSH_LOG_RARE, | ||
| 223 | + "The file %s is too large (%jd MB > %zu MB): skipping", | ||
| 224 | + filename, | ||
| 225 | + (intmax_t)sb.st_size / 1024 / 1024, | ||
| 226 | + max_file_size / 1024 / 1024); | ||
| 227 | + close(fd); | ||
| 228 | + return NULL; | ||
| 229 | + } | ||
| 230 | + | ||
| 231 | + f = fdopen(fd, "r"); | ||
| 232 | + if (f == NULL) { | ||
| 233 | + SSH_LOG(SSH_LOG_RARE, | ||
| 234 | + "Failed to open a file %s for reading: %s", | ||
| 235 | + filename, | ||
| 236 | + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); | ||
| 237 | + close(fd); | ||
| 238 | + return NULL; | ||
| 239 | + } | ||
| 240 | + | ||
| 241 | + /* the flcose() will close also the underlying fd */ | ||
| 242 | + return f; | ||
| 243 | +} | ||
| 244 | + | ||
| 245 | /** @} */ | ||
| 246 | diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c | ||
| 247 | index fcfe8fbc..0cb31a76 100644 | ||
| 248 | --- a/tests/unittests/torture_config.c | ||
| 249 | +++ b/tests/unittests/torture_config.c | ||
| 250 | @@ -2675,6 +2675,23 @@ static void torture_config_match_complex(void **state) | ||
| 251 | ssh_string_free_char(v); | ||
| 252 | } | ||
| 253 | |||
| 254 | +/* Invalid configuration files | ||
| 255 | + */ | ||
| 256 | +static void torture_config_invalid(void **state) | ||
| 257 | +{ | ||
| 258 | + ssh_session session = *state; | ||
| 259 | + | ||
| 260 | + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); | ||
| 261 | + | ||
| 262 | + /* non-regular file -- ignored (or missing on non-unix) so OK */ | ||
| 263 | + _parse_config(session, "/dev/random", NULL, SSH_OK); | ||
| 264 | + | ||
| 265 | +#ifndef _WIN32 | ||
| 266 | + /* huge file -- ignored (or missing on non-unix) so OK */ | ||
| 267 | + _parse_config(session, "/proc/kcore", NULL, SSH_OK); | ||
| 268 | +#endif | ||
| 269 | +} | ||
| 270 | + | ||
| 271 | int torture_run_tests(void) | ||
| 272 | { | ||
| 273 | int rc; | ||
| 274 | @@ -2771,6 +2788,9 @@ int torture_run_tests(void) | ||
| 275 | setup, teardown), | ||
| 276 | cmocka_unit_test_setup_teardown(torture_config_match_complex, | ||
| 277 | setup, teardown), | ||
| 278 | + cmocka_unit_test_setup_teardown(torture_config_invalid, | ||
| 279 | + setup, | ||
| 280 | + teardown), | ||
| 281 | }; | ||
| 282 | |||
| 283 | |||
| 284 | -- | ||
| 285 | 2.51.0 | ||
| 286 | |||
diff --git a/meta-oe/recipes-support/libssh/libssh_0.11.3.bb b/meta-oe/recipes-support/libssh/libssh_0.11.3.bb index 193ff3512d..c552692bde 100644 --- a/meta-oe/recipes-support/libssh/libssh_0.11.3.bb +++ b/meta-oe/recipes-support/libssh/libssh_0.11.3.bb | |||
| @@ -14,6 +14,7 @@ SRC_URI = "git://git.libssh.org/projects/libssh.git;protocol=https;branch=stable | |||
| 14 | file://CVE-2026-0968_p1.patch \ | 14 | file://CVE-2026-0968_p1.patch \ |
| 15 | file://CVE-2026-0968_p2.patch \ | 15 | file://CVE-2026-0968_p2.patch \ |
| 16 | file://CVE-2026-0967.patch \ | 16 | file://CVE-2026-0967.patch \ |
| 17 | file://CVE-2026-0965.patch \ | ||
| 17 | " | 18 | " |
| 18 | 19 | ||
| 19 | SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch" | 20 | SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch" |
