summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch284
-rw-r--r--meta-oe/recipes-support/libssh/libssh_0.10.6.bb1
2 files changed, 285 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..c30310bc70
--- /dev/null
+++ b/meta-oe/recipes-support/libssh/libssh/CVE-2026-0965.patch
@@ -0,0 +1,284 @@
1From cc84dbc554e4e3d760234ea0e24284ef09fd3428 Mon Sep 17 00:00:00 2001
2From: Jakub Jelen <jjelen@redhat.com>
3Date: Thu, 11 Dec 2025 17:33:19 +0100
4Subject: [PATCH] CVE-2026-0965 config: Do not attempt to read non-regular and
5 too large configuration files
6
7Changes also the reading of known_hosts to use the new helper function
8
9Signed-off-by: Jakub Jelen <jjelen@redhat.com>
10Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
11(cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798)
12
13CVE: CVE-2026-0965
14Upstream-Status: Backport [https://git.libssh.org/projects/libssh.git/commit/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76]
15
16Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
17---
18 include/libssh/misc.h | 4 +-
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(+), 11 deletions(-)
28
29diff --git a/include/libssh/misc.h b/include/libssh/misc.h
30index 0924ba7f..5591c925 100644
31--- a/include/libssh/misc.h
32+++ b/include/libssh/misc.h
33@@ -20,7 +20,7 @@
34
35 #ifndef MISC_H_
36 #define MISC_H_
37-
38+#include <stdio.h>
39 #ifdef __cplusplus
40 extern "C" {
41 #endif
42@@ -106,6 +106,8 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *repl);
43
44 int ssh_check_hostname_syntax(const char *hostname);
45
46+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
47+
48 #ifdef __cplusplus
49 }
50 #endif
51diff --git a/include/libssh/priv.h b/include/libssh/priv.h
52index 47af57f4..b55df501 100644
53--- a/include/libssh/priv.h
54+++ b/include/libssh/priv.h
55@@ -438,6 +438,9 @@ bool is_ssh_initialized(void);
56 #define SSH_ERRNO_MSG_MAX 1024
57 char *ssh_strerror(int err_num, char *buf, size_t buflen);
58
59+/** The default maximum file size for a configuration file */
60+#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024
61+
62 #ifdef __cplusplus
63 }
64 #endif
65diff --git a/src/bind_config.c b/src/bind_config.c
66index ed42cbe3..c429bce2 100644
67--- a/src/bind_config.c
68+++ b/src/bind_config.c
69@@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind,
70 return;
71 }
72
73- f = fopen(filename, "r");
74+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
75 if (f == NULL) {
76 SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
77 filename);
78@@ -636,7 +636,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
79 * option to be redefined later by another file. */
80 uint8_t seen[BIND_CFG_MAX] = {0};
81
82- f = fopen(filename, "r");
83+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
84 if (f == NULL) {
85 return 0;
86 }
87diff --git a/src/config.c b/src/config.c
88index d4d8d419..87cdaaaf 100644
89--- a/src/config.c
90+++ b/src/config.c
91@@ -215,10 +215,9 @@ local_parse_file(ssh_session session,
92 return;
93 }
94
95- f = fopen(filename, "r");
96+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
97 if (f == NULL) {
98- SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
99- filename);
100+ /* The underlying function logs the reasons */
101 return;
102 }
103
104@@ -1205,8 +1204,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
105 int parsing, rv;
106 bool global = 0;
107
108- f = fopen(filename, "r");
109+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
110 if (f == NULL) {
111+ /* The underlying function logs the reasons */
112 return 0;
113 }
114
115diff --git a/src/dh-gex.c b/src/dh-gex.c
116index 642a88ae..aadc7c09 100644
117--- a/src/dh-gex.c
118+++ b/src/dh-gex.c
119@@ -520,9 +520,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file,
120 }
121
122 if (moduli_file != NULL)
123- moduli = fopen(moduli_file, "r");
124+ moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE);
125 else
126- moduli = fopen(MODULI_FILE, "r");
127+ moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE);
128
129 if (moduli == NULL) {
130 char err_msg[SSH_ERRNO_MSG_MAX] = {0};
131diff --git a/src/known_hosts.c b/src/known_hosts.c
132index f660a6f3..ba2ae4d5 100644
133--- a/src/known_hosts.c
134+++ b/src/known_hosts.c
135@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
136 struct ssh_tokens_st *tokens = NULL;
137
138 if (*file == NULL) {
139- *file = fopen(filename,"r");
140+ *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
141 if (*file == NULL) {
142 return NULL;
143 }
144diff --git a/src/knownhosts.c b/src/knownhosts.c
145index 109b4f06..f0fde696 100644
146--- a/src/knownhosts.c
147+++ b/src/knownhosts.c
148@@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries(const char *match,
149 FILE *fp = NULL;
150 int rc;
151
152- fp = fopen(filename, "r");
153+ fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
154 if (fp == NULL) {
155 char err_msg[SSH_ERRNO_MSG_MAX] = {0};
156 SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s",
157diff --git a/src/misc.c b/src/misc.c
158index 565abcfc..e78c92ba 100644
159--- a/src/misc.c
160+++ b/src/misc.c
161@@ -37,6 +37,7 @@
162 #endif /* _WIN32 */
163
164 #include <errno.h>
165+#include <fcntl.h>
166 #include <limits.h>
167 #include <stdio.h>
168 #include <string.h>
169@@ -2074,4 +2075,77 @@ int ssh_check_hostname_syntax(const char *hostname)
170 return SSH_OK;
171 }
172
173+/**
174+ * @internal
175+ *
176+ * @brief Safely open a file containing some configuration.
177+ *
178+ * Runs checks if the file can be used as some configuration file (is regular
179+ * file and is not too large). If so, returns the opened file (for reading).
180+ * Otherwise logs error and returns `NULL`.
181+ *
182+ * @param filename The path to the file to open.
183+ * @param max_file_size Maximum file size that is accepted.
184+ *
185+ * @returns the opened file or `NULL` on error.
186+ */
187+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
188+{
189+ FILE *f = NULL;
190+ struct stat sb;
191+ char err_msg[SSH_ERRNO_MSG_MAX] = {0};
192+ int r, fd;
193+
194+ /* open first to avoid TOCTOU */
195+ fd = open(filename, O_RDONLY);
196+ if (fd == -1) {
197+ SSH_LOG(SSH_LOG_RARE,
198+ "Failed to open a file %s for reading: %s",
199+ filename,
200+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
201+ return NULL;
202+ }
203+
204+ /* Check the file is sensible for a configuration file */
205+ r = fstat(fd, &sb);
206+ if (r != 0) {
207+ SSH_LOG(SSH_LOG_RARE,
208+ "Failed to stat %s: %s",
209+ filename,
210+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
211+ close(fd);
212+ return NULL;
213+ }
214+ if ((sb.st_mode & S_IFMT) != S_IFREG) {
215+ SSH_LOG(SSH_LOG_RARE,
216+ "The file %s is not a regular file: skipping",
217+ filename);
218+ close(fd);
219+ return NULL;
220+ }
221+
222+ if ((size_t)sb.st_size > max_file_size) {
223+ SSH_LOG(SSH_LOG_RARE,
224+ "The file %s is too large (%jd MB > %zu MB): skipping",
225+ filename,
226+ (intmax_t)sb.st_size / 1024 / 1024,
227+ max_file_size / 1024 / 1024);
228+ close(fd);
229+ return NULL;
230+ }
231+
232+ f = fdopen(fd, "r");
233+ if (f == NULL) {
234+ SSH_LOG(SSH_LOG_RARE,
235+ "Failed to open a file %s for reading: %s",
236+ filename,
237+ ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));
238+ close(fd);
239+ return NULL;
240+ }
241+
242+ /* the flcose() will close also the underlying fd */
243+ return f;
244+}
245+
246 /** @} */
247diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
248index 3569b51a..b4c7b0a7 100644
249--- a/tests/unittests/torture_config.c
250+++ b/tests/unittests/torture_config.c
251@@ -1908,6 +1908,23 @@ static void torture_config_make_absolute_no_sshdir(void **state)
252 torture_config_make_absolute_int(state, 1);
253 }
254
255+/* Invalid configuration files
256+ */
257+static void torture_config_invalid(void **state)
258+{
259+ ssh_session session = *state;
260+
261+ ssh_options_set(session, SSH_OPTIONS_HOST, "Bar");
262+
263+ /* non-regular file -- ignored (or missing on non-unix) so OK */
264+ _parse_config(session, "/dev/random", NULL, SSH_OK);
265+
266+#ifndef _WIN32
267+ /* huge file -- ignored (or missing on non-unix) so OK */
268+ _parse_config(session, "/proc/kcore", NULL, SSH_OK);
269+#endif
270+}
271+
272 int torture_run_tests(void)
273 {
274 int rc;
275@@ -1980,6 +1997,9 @@ int torture_run_tests(void)
276 setup, teardown),
277 cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir,
278 setup_no_sshdir, teardown),
279+ cmocka_unit_test_setup_teardown(torture_config_invalid,
280+ setup,
281+ teardown),
282 };
283
284
diff --git a/meta-oe/recipes-support/libssh/libssh_0.10.6.bb b/meta-oe/recipes-support/libssh/libssh_0.10.6.bb
index e4a28af7a6..189305fd2e 100644
--- a/meta-oe/recipes-support/libssh/libssh_0.10.6.bb
+++ b/meta-oe/recipes-support/libssh/libssh_0.10.6.bb
@@ -31,6 +31,7 @@ SRC_URI = "git://git.libssh.org/projects/libssh.git;protocol=https;branch=stable
31 file://CVE-2026-0968-1.patch \ 31 file://CVE-2026-0968-1.patch \
32 file://CVE-2026-0968-2.patch \ 32 file://CVE-2026-0968-2.patch \
33 file://CVE-2026-0967.patch \ 33 file://CVE-2026-0967.patch \
34 file://CVE-2026-0965.patch \
34 " 35 "
35SRCREV = "10e09e273f69e149389b3e0e5d44b8c221c2e7f6" 36SRCREV = "10e09e273f69e149389b3e0e5d44b8c221c2e7f6"
36 37