summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch286
-rw-r--r--meta-oe/recipes-support/libssh/libssh_0.11.3.bb1
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 @@
1From 5858a988942d2e25985b34b8c40ce2792cbbe853 Mon Sep 17 00:00:00 2001
2From: Jakub Jelen <jjelen@redhat.com>
3Date: Thu, 11 Dec 2025 17:33:19 +0100
4Subject: [PATCH 4/4] CVE-2026-0965 config: Do not attempt to read non-regular
5 and too large configuration files
6
7Changes also the reading of known_hosts to use the new helper function
8
9CVE: CVE-2026-0965
10Upstream-Status: Backport [https://git.libssh.org/projects/libssh.git/commit/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76]
11
12Signed-off-by: Jakub Jelen <jjelen@redhat.com>
13Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
14(cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798)
15(cherry picked from commit bf390a042623e02abc8f421c4c5fadc0429a8a76)
16Signed-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
29diff --git a/include/libssh/misc.h b/include/libssh/misc.h
30index 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
50diff --git a/include/libssh/priv.h b/include/libssh/priv.h
51index 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
64diff --git a/src/bind_config.c b/src/bind_config.c
65index 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 }
86diff --git a/src/config.c b/src/config.c
87index 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
114diff --git a/src/dh-gex.c b/src/dh-gex.c
115index 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};
130diff --git a/src/known_hosts.c b/src/known_hosts.c
131index 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 }
143diff --git a/src/knownhosts.c b/src/knownhosts.c
144index 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",
156diff --git a/src/misc.c b/src/misc.c
157index 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 /** @} */
246diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
247index 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--
2852.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
19SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch" 20SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch"