|
| 1 | +From 268894dd119cab18560a88dc774baa0129650be9 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Azure Linux Security Servicing Account |
| 3 | + <azurelinux-security@microsoft.com> |
| 4 | +Date: Wed, 1 Apr 2026 19:50:22 +0000 |
| 5 | +Subject: [PATCH] Patch CVE-2026-0965 for libssh (applied via patch -p1) |
| 6 | + |
| 7 | +Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> |
| 8 | +Upstream-reference: https://git.libssh.org/projects/libssh.git/patch/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76 |
| 9 | +--- |
| 10 | + include/libssh/misc.h | 4 ++ |
| 11 | + include/libssh/priv.h | 3 ++ |
| 12 | + src/bind_config.c | 4 +- |
| 13 | + src/config.c | 8 ++-- |
| 14 | + src/dh-gex.c | 4 +- |
| 15 | + src/known_hosts.c | 2 +- |
| 16 | + src/knownhosts.c | 2 +- |
| 17 | + src/misc.c | 74 ++++++++++++++++++++++++++++++++ |
| 18 | + tests/unittests/torture_config.c | 19 ++++++++ |
| 19 | + 9 files changed, 110 insertions(+), 10 deletions(-) |
| 20 | + |
| 21 | +diff --git a/include/libssh/misc.h b/include/libssh/misc.h |
| 22 | +index 0924ba7..27bdc2d 100644 |
| 23 | +--- a/include/libssh/misc.h |
| 24 | ++++ b/include/libssh/misc.h |
| 25 | +@@ -21,6 +21,8 @@ |
| 26 | + #ifndef MISC_H_ |
| 27 | + #define MISC_H_ |
| 28 | + |
| 29 | ++#include <stdio.h> |
| 30 | ++ |
| 31 | + #ifdef __cplusplus |
| 32 | + extern "C" { |
| 33 | + #endif |
| 34 | +@@ -106,6 +108,8 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *repl); |
| 35 | + |
| 36 | + int ssh_check_hostname_syntax(const char *hostname); |
| 37 | + |
| 38 | ++FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); |
| 39 | ++ |
| 40 | + #ifdef __cplusplus |
| 41 | + } |
| 42 | + #endif |
| 43 | +diff --git a/include/libssh/priv.h b/include/libssh/priv.h |
| 44 | +index 47af57f..b55df50 100644 |
| 45 | +--- a/include/libssh/priv.h |
| 46 | ++++ b/include/libssh/priv.h |
| 47 | +@@ -438,6 +438,9 @@ bool is_ssh_initialized(void); |
| 48 | + #define SSH_ERRNO_MSG_MAX 1024 |
| 49 | + char *ssh_strerror(int err_num, char *buf, size_t buflen); |
| 50 | + |
| 51 | ++/** The default maximum file size for a configuration file */ |
| 52 | ++#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 |
| 53 | ++ |
| 54 | + #ifdef __cplusplus |
| 55 | + } |
| 56 | + #endif |
| 57 | +diff --git a/src/bind_config.c b/src/bind_config.c |
| 58 | +index ed42cbe..c429bce 100644 |
| 59 | +--- a/src/bind_config.c |
| 60 | ++++ b/src/bind_config.c |
| 61 | +@@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind, |
| 62 | + return; |
| 63 | + } |
| 64 | + |
| 65 | +- f = fopen(filename, "r"); |
| 66 | ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 67 | + if (f == NULL) { |
| 68 | + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", |
| 69 | + filename); |
| 70 | +@@ -636,7 +636,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) |
| 71 | + * option to be redefined later by another file. */ |
| 72 | + uint8_t seen[BIND_CFG_MAX] = {0}; |
| 73 | + |
| 74 | +- f = fopen(filename, "r"); |
| 75 | ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 76 | + if (f == NULL) { |
| 77 | + return 0; |
| 78 | + } |
| 79 | +diff --git a/src/config.c b/src/config.c |
| 80 | +index d4d8d41..87cdaaa 100644 |
| 81 | +--- a/src/config.c |
| 82 | ++++ b/src/config.c |
| 83 | +@@ -215,10 +215,9 @@ local_parse_file(ssh_session session, |
| 84 | + return; |
| 85 | + } |
| 86 | + |
| 87 | +- f = fopen(filename, "r"); |
| 88 | ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 89 | + if (f == NULL) { |
| 90 | +- SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", |
| 91 | +- filename); |
| 92 | ++ /* The underlying function logs the reasons */ |
| 93 | + return; |
| 94 | + } |
| 95 | + |
| 96 | +@@ -1205,8 +1204,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename) |
| 97 | + int parsing, rv; |
| 98 | + bool global = 0; |
| 99 | + |
| 100 | +- f = fopen(filename, "r"); |
| 101 | ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 102 | + if (f == NULL) { |
| 103 | ++ /* The underlying function logs the reasons */ |
| 104 | + return 0; |
| 105 | + } |
| 106 | + |
| 107 | +diff --git a/src/dh-gex.c b/src/dh-gex.c |
| 108 | +index 642a88a..aadc7c0 100644 |
| 109 | +--- a/src/dh-gex.c |
| 110 | ++++ b/src/dh-gex.c |
| 111 | +@@ -520,9 +520,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file, |
| 112 | + } |
| 113 | + |
| 114 | + if (moduli_file != NULL) |
| 115 | +- moduli = fopen(moduli_file, "r"); |
| 116 | ++ moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); |
| 117 | + else |
| 118 | +- moduli = fopen(MODULI_FILE, "r"); |
| 119 | ++ moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); |
| 120 | + |
| 121 | + if (moduli == NULL) { |
| 122 | + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
| 123 | +diff --git a/src/known_hosts.c b/src/known_hosts.c |
| 124 | +index f660a6f..ba2ae4d 100644 |
| 125 | +--- a/src/known_hosts.c |
| 126 | ++++ b/src/known_hosts.c |
| 127 | +@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, |
| 128 | + struct ssh_tokens_st *tokens = NULL; |
| 129 | + |
| 130 | + if (*file == NULL) { |
| 131 | +- *file = fopen(filename,"r"); |
| 132 | ++ *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 133 | + if (*file == NULL) { |
| 134 | + return NULL; |
| 135 | + } |
| 136 | +diff --git a/src/knownhosts.c b/src/knownhosts.c |
| 137 | +index 109b4f0..f0fde69 100644 |
| 138 | +--- a/src/knownhosts.c |
| 139 | ++++ b/src/knownhosts.c |
| 140 | +@@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries(const char *match, |
| 141 | + FILE *fp = NULL; |
| 142 | + int rc; |
| 143 | + |
| 144 | +- fp = fopen(filename, "r"); |
| 145 | ++ fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); |
| 146 | + if (fp == NULL) { |
| 147 | + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
| 148 | + SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", |
| 149 | +diff --git a/src/misc.c b/src/misc.c |
| 150 | +index f371f33..d936385 100644 |
| 151 | +--- a/src/misc.c |
| 152 | ++++ b/src/misc.c |
| 153 | +@@ -37,6 +37,7 @@ |
| 154 | + #endif /* _WIN32 */ |
| 155 | + |
| 156 | + #include <errno.h> |
| 157 | ++#include <fcntl.h> |
| 158 | + #include <limits.h> |
| 159 | + #include <stdio.h> |
| 160 | + #include <string.h> |
| 161 | +@@ -2074,4 +2075,77 @@ int ssh_check_hostname_syntax(const char *hostname) |
| 162 | + return SSH_OK; |
| 163 | + } |
| 164 | + |
| 165 | ++/** |
| 166 | ++ * @internal |
| 167 | ++ * |
| 168 | ++ * @brief Safely open a file containing some configuration. |
| 169 | ++ * |
| 170 | ++ * Runs checks if the file can be used as some configuration file (is regular |
| 171 | ++ * file and is not too large). If so, returns the opened file (for reading). |
| 172 | ++ * Otherwise logs error and returns `NULL`. |
| 173 | ++ * |
| 174 | ++ * @param filename The path to the file to open. |
| 175 | ++ * @param max_file_size Maximum file size that is accepted. |
| 176 | ++ * |
| 177 | ++ * @returns the opened file or `NULL` on error. |
| 178 | ++ */ |
| 179 | ++FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) |
| 180 | ++{ |
| 181 | ++ FILE *f = NULL; |
| 182 | ++ struct stat sb; |
| 183 | ++ char err_msg[SSH_ERRNO_MSG_MAX] = {0}; |
| 184 | ++ int r, fd; |
| 185 | ++ |
| 186 | ++ /* open first to avoid TOCTOU */ |
| 187 | ++ fd = open(filename, O_RDONLY); |
| 188 | ++ if (fd == -1) { |
| 189 | ++ SSH_LOG(SSH_LOG_RARE, |
| 190 | ++ "Failed to open a file %s for reading: %s", |
| 191 | ++ filename, |
| 192 | ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
| 193 | ++ return NULL; |
| 194 | ++ } |
| 195 | ++ |
| 196 | ++ /* Check the file is sensible for a configuration file */ |
| 197 | ++ r = fstat(fd, &sb); |
| 198 | ++ if (r != 0) { |
| 199 | ++ SSH_LOG(SSH_LOG_RARE, |
| 200 | ++ "Failed to stat %s: %s", |
| 201 | ++ filename, |
| 202 | ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); |
| 203 | ++ close(fd); |
| 204 | ++ return NULL; |
| 205 | ++ } |
| 206 | ++ if ((sb.st_mode & S_IFMT) != S_IFREG) { |
| 207 | ++ SSH_LOG(SSH_LOG_RARE, |
| 208 | ++ "The file %s is not a regular file: skipping", |
| 209 | ++ filename); |
| 210 | ++ close(fd); |
| 211 | ++ return NULL; |
| 212 | ++ } |
| 213 | ++ |
| 214 | ++ if ((size_t)sb.st_size > max_file_size) { |
| 215 | ++ SSH_LOG(SSH_LOG_RARE, |
| 216 | ++ "The file %s is too large (%jd MB > %zu MB): skipping", |
| 217 | ++ filename, |
| 218 | ++ (intmax_t)sb.st_size / 1024 / 1024, |
| 219 | ++ max_file_size / 1024 / 1024); |
| 220 | ++ close(fd); |
| 221 | ++ return NULL; |
| 222 | ++ } |
| 223 | ++ |
| 224 | ++ f = fdopen(fd, "r"); |
| 225 | ++ if (f == NULL) { |
| 226 | ++ SSH_LOG(SSH_LOG_RARE, |
| 227 | ++ "Failed to open a file %s for reading: %s", |
| 228 | ++ filename, |
| 229 | ++ ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); |
| 230 | ++ close(fd); |
| 231 | ++ return NULL; |
| 232 | ++ } |
| 233 | ++ |
| 234 | ++ /* the flcose() will close also the underlying fd */ |
| 235 | ++ return f; |
| 236 | ++} |
| 237 | ++ |
| 238 | + /** @} */ |
| 239 | +diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c |
| 240 | +index b7c763a..b218f9e 100644 |
| 241 | +--- a/tests/unittests/torture_config.c |
| 242 | ++++ b/tests/unittests/torture_config.c |
| 243 | +@@ -1850,6 +1850,22 @@ static void torture_config_make_absolute_no_sshdir(void **state) |
| 244 | + torture_config_make_absolute_int(state, 1); |
| 245 | + } |
| 246 | + |
| 247 | ++/* Invalid configuration files */ |
| 248 | ++static void torture_config_invalid(void **state) |
| 249 | ++{ |
| 250 | ++ ssh_session session = *state; |
| 251 | ++ |
| 252 | ++ ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); |
| 253 | ++ |
| 254 | ++ /* non-regular file -- ignored (or missing on non-unix) so OK */ |
| 255 | ++ _parse_config(session, "/dev/random", NULL, SSH_OK); |
| 256 | ++ |
| 257 | ++#ifndef _WIN32 |
| 258 | ++ /* huge file -- ignored (or missing on non-unix) so OK */ |
| 259 | ++ _parse_config(session, "/proc/kcore", NULL, SSH_OK); |
| 260 | ++#endif |
| 261 | ++} |
| 262 | ++ |
| 263 | + int torture_run_tests(void) |
| 264 | + { |
| 265 | + int rc; |
| 266 | +@@ -1922,6 +1938,9 @@ int torture_run_tests(void) |
| 267 | + setup, teardown), |
| 268 | + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, |
| 269 | + setup_no_sshdir, teardown), |
| 270 | ++ cmocka_unit_test_setup_teardown(torture_config_invalid, |
| 271 | ++ setup, |
| 272 | ++ teardown), |
| 273 | + }; |
| 274 | + |
| 275 | + |
| 276 | +-- |
| 277 | +2.45.4 |
| 278 | + |
0 commit comments