Skip to content

Commit 19de5a0

Browse files
[AutoPR- Security] Patch libssh for CVE-2026-0967, CVE-2026-0966, CVE-2026-0965, CVE-2026-0964 [MEDIUM] (#16410)
Co-authored-by: Aditya Singh <v-aditysing@microsoft.com>
1 parent dd41975 commit 19de5a0

5 files changed

Lines changed: 723 additions & 1 deletion

File tree

SPECS/libssh/CVE-2026-0964.patch

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
From 89fc59935d26f32bbd6d745c2042eb4e888e1b67 Mon Sep 17 00:00:00 2001
2+
From: Jakub Jelen <jjelen@redhat.com>
3+
Date: Mon, 22 Dec 2025 19:16:44 +0100
4+
Subject: [PATCH] CVE-2026-0964 scp: Reject invalid paths received through scp
5+
6+
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
7+
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
8+
(cherry picked from commit daa80818f89347b4d80b0c5b80659f9a9e55e8cc)
9+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
10+
Upstream-reference: https://git.libssh.org/projects/libssh.git/patch/?id=a5e4b12090b0c939d85af4f29280e40c5b6600aa
11+
---
12+
src/scp.c | 16 ++++++++++++++++
13+
1 file changed, 16 insertions(+)
14+
15+
diff --git a/src/scp.c b/src/scp.c
16+
index 103822c..c23b7b1 100644
17+
--- a/src/scp.c
18+
+++ b/src/scp.c
19+
@@ -848,6 +848,22 @@ int ssh_scp_pull_request(ssh_scp scp)
20+
size = strtoull(tmp, NULL, 10);
21+
p++;
22+
name = strdup(p);
23+
+ /* Catch invalid name:
24+
+ * - empty ones
25+
+ * - containing any forward slash -- directory traversal handled
26+
+ * differently
27+
+ * - special names "." and ".." referring to the current and parent
28+
+ * directories -- they are not expected either
29+
+ */
30+
+ if (name == NULL || name[0] == '\0' || strchr(name, '/') ||
31+
+ strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
32+
+ ssh_set_error(scp->session,
33+
+ SSH_FATAL,
34+
+ "Received invalid filename: %s",
35+
+ name == NULL ? "<NULL>" : name);
36+
+ SAFE_FREE(name);
37+
+ goto error;
38+
+ }
39+
SAFE_FREE(scp->request_name);
40+
scp->request_name = name;
41+
if (buffer[0] == 'C') {
42+
--
43+
2.45.4
44+

SPECS/libssh/CVE-2026-0965.patch

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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+

SPECS/libssh/CVE-2026-0966.patch

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
From 1fd7f07af7dc69d4b86dabde62dc5ecbf449cfd7 Mon Sep 17 00:00:00 2001
2+
From: Jakub Jelen <jjelen@redhat.com>
3+
Date: Thu, 8 Jan 2026 12:09:50 +0100
4+
Subject: [PATCH] CVE-2026-0966 misc: Avoid heap buffer underflow in
5+
ssh_get_hexa
6+
MIME-Version: 1.0
7+
Content-Type: text/plain; charset=UTF-8
8+
Content-Transfer-Encoding: 8bit
9+
10+
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
11+
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
12+
(cherry picked from commit 417a095e6749a1f3635e02332061edad3c6a3401)
13+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
14+
Upstream-reference: https://git.libssh.org/projects/libssh.git/patch/?id=6ba5ff1b7b1547a59f750fbc06b89737b7456117
15+
---
16+
src/misc.c | 2 +-
17+
1 file changed, 1 insertion(+), 1 deletion(-)
18+
19+
diff --git a/src/misc.c b/src/misc.c
20+
index d936385..e78c92b 100644
21+
--- a/src/misc.c
22+
+++ b/src/misc.c
23+
@@ -452,7 +452,7 @@ char *ssh_get_hexa(const unsigned char *what, size_t len)
24+
size_t i;
25+
size_t hlen = len * 3;
26+
27+
- if (len > (UINT_MAX - 1) / 3) {
28+
+ if (what == NULL || len < 1 || len > (UINT_MAX - 1) / 3) {
29+
return NULL;
30+
}
31+
32+
--
33+
2.45.4
34+

0 commit comments

Comments
 (0)