Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
libssh (0.11.3-1deepin5) unstable; urgency=medium

* fix(cve): CVE-2026-0965

-- deepin-ci-robot <packages@deepin.org> Tue, 21 Jul 2026 10:21:34 +0800

libssh (0.11.3-1deepin4) unstable; urgency=medium

* fix(cve): CVE-2026-0968

-- deepin-ci-robot <packages@deepin.org> Tue, 21 Jul 2026 10:14:36 +0800

libssh (0.11.3-1deepin3) unstable; urgency=medium

* fix(cve): CVE-2026-0964

-- deepin-ci-robot <packages@deepin.org> Tue, 21 Jul 2026 10:08:08 +0800

libssh (0.11.3-1deepin2) unstable; urgency=medium

* fix(cve): CVE-2026-0967

-- deepin-ci-robot <packages@deepin.org> Sat, 18 Jul 2026 08:44:40 +0800

libssh (0.11.3-1deepin1) unstable; urgency=medium

* Fix CVE-2026-3731: out-of-bounds read in sftp_extensions_get_name
Expand Down
34 changes: 34 additions & 0 deletions debian/patches/CVE-2026-0964.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Description: CVE-2026-0964 - 安全修复
Author: Jakub Jelen <jjelen@redhat.com>
Origin: https://gitlab.com/libssh/libssh-mirror/commit/a5e4b12090b0c939d85af4f29280e40c5b6600aa
Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-0964
Last-Update: Mon, 22 Dec 2025 19:16:44 +0100
---

diff --git a/src/scp.c b/src/scp.c
index a1e3687..08a29fa 100644
--- a/src/scp.c
+++ b/src/scp.c
@@ -862,6 +862,22 @@ int ssh_scp_pull_request(ssh_scp scp)
size = strtoull(tmp, NULL, 10);
p++;
name = strdup(p);
+ /* Catch invalid name:
+ * - empty ones
+ * - containing any forward slash -- directory traversal handled
+ * differently
+ * - special names "." and ".." referring to the current and parent
+ * directories -- they are not expected either
+ */
+ if (name == NULL || name[0] == '\0' || strchr(name, '/') ||
+ strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
+ ssh_set_error(scp->session,
+ SSH_FATAL,
+ "Received invalid filename: %s",
+ name == NULL ? "<NULL>" : name);
+ SAFE_FREE(name);
+ goto error;
+ }
SAFE_FREE(scp->request_name);
scp->request_name = name;
if (buffer[0] == 'C') {
262 changes: 262 additions & 0 deletions debian/patches/CVE-2026-0965.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
Description: CVE-2026-0965 - 安全修复
Author: Jakub Jelen <jjelen@redhat.com>
Origin: https://git.libssh.org/projects/libssh/commit/bf390a042623e02abc8f421c4c5fadc0429a8a76
Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-0965
Last-Update: 2025-12-11
---

diff --git a/include/libssh/misc.h b/include/libssh/misc.h
index ab726a0..8eab94e 100644
--- a/include/libssh/misc.h
+++ b/include/libssh/misc.h
@@ -36,6 +36,7 @@
#include <sys/types.h>
#include <stdbool.h>
#endif /* _WIN32 */
+#include <stdio.h>

#ifdef __cplusplus
extern "C" {
@@ -136,6 +137,8 @@ int ssh_check_username_syntax(const char *username);
void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list);
bool ssh_libssh_proxy_jumps(void);

+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
+
#ifdef __cplusplus
}
#endif
diff --git a/include/libssh/priv.h b/include/libssh/priv.h
index 35fd850..6206997 100644
--- a/include/libssh/priv.h
+++ b/include/libssh/priv.h
@@ -473,6 +473,9 @@ char *ssh_strerror(int err_num, char *buf, size_t buflen);
#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1)
int encode_current_tty_opts(unsigned char *buf, size_t buflen);

+/** The default maximum file size for a configuration file */
+#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024
+
#ifdef __cplusplus
}
#endif
diff --git a/src/bind_config.c b/src/bind_config.c
index 9e4a7fd..c12f100 100644
--- a/src/bind_config.c
+++ b/src/bind_config.c
@@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind,
return;
}

- f = fopen(filename, "r");
+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) {
SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
filename);
@@ -636,7 +636,7 @@ int ssh_bind_config_parse_file(ssh_bind bind, const char *filename)
* option to be redefined later by another file. */
uint8_t seen[BIND_CFG_MAX] = {0};

- f = fopen(filename, "r");
+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) {
return 0;
}
diff --git a/src/config.c b/src/config.c
index b4171ef..1ffad53 100644
--- a/src/config.c
+++ b/src/config.c
@@ -223,10 +223,9 @@ local_parse_file(ssh_session session,
return;
}

- f = fopen(filename, "r");
+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) {
- SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
- filename);
+ /* The underlying function logs the reasons */
return;
}

@@ -1466,8 +1465,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
int parsing, rv;
bool global = 0;

- f = fopen(filename, "r");
+ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) {
+ /* The underlying function logs the reasons */
return 0;
}

diff --git a/src/dh-gex.c b/src/dh-gex.c
index 46ba934..428a565 100644
--- a/src/dh-gex.c
+++ b/src/dh-gex.c
@@ -519,9 +519,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file,
}

if (moduli_file != NULL)
- moduli = fopen(moduli_file, "r");
+ moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE);
else
- moduli = fopen(MODULI_FILE, "r");
+ moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE);

if (moduli == NULL) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
diff --git a/src/known_hosts.c b/src/known_hosts.c
index 3ef83e2..701576c 100644
--- a/src/known_hosts.c
+++ b/src/known_hosts.c
@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
struct ssh_tokens_st *tokens = NULL;

if (*file == NULL) {
- *file = fopen(filename,"r");
+ *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (*file == NULL) {
return NULL;
}
diff --git a/src/knownhosts.c b/src/knownhosts.c
index a2d08a7..3ab468d 100644
--- a/src/knownhosts.c
+++ b/src/knownhosts.c
@@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries(const char *match,
FILE *fp = NULL;
int rc;

- fp = fopen(filename, "r");
+ fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (fp == NULL) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s",
diff --git a/src/misc.c b/src/misc.c
index 774211f..3968e6b 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -37,6 +37,7 @@
#endif /* _WIN32 */

#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
@@ -2244,4 +2245,77 @@ ssh_libssh_proxy_jumps(void)
return !(t != NULL && t[0] == '1');
}

+/**
+ * @internal
+ *
+ * @brief Safely open a file containing some configuration.
+ *
+ * Runs checks if the file can be used as some configuration file (is regular
+ * file and is not too large). If so, returns the opened file (for reading).
+ * Otherwise logs error and returns `NULL`.
+ *
+ * @param filename The path to the file to open.
+ * @param max_file_size Maximum file size that is accepted.
+ *
+ * @returns the opened file or `NULL` on error.
+ */
+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
+{
+ FILE *f = NULL;
+ struct stat sb;
+ char err_msg[SSH_ERRNO_MSG_MAX] = {0};
+ int r, fd;
+
+ /* open first to avoid TOCTOU */
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ SSH_LOG(SSH_LOG_RARE,
+ "Failed to open a file %s for reading: %s",
+ filename,
+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
+ return NULL;
+ }
+
+ /* Check the file is sensible for a configuration file */
+ r = fstat(fd, &sb);
+ if (r != 0) {
+ SSH_LOG(SSH_LOG_RARE,
+ "Failed to stat %s: %s",
+ filename,
+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
+ close(fd);
+ return NULL;
+ }
+ if ((sb.st_mode & S_IFMT) != S_IFREG) {
+ SSH_LOG(SSH_LOG_RARE,
+ "The file %s is not a regular file: skipping",
+ filename);
+ close(fd);
+ return NULL;
+ }
+
+ if ((size_t)sb.st_size > max_file_size) {
+ SSH_LOG(SSH_LOG_RARE,
+ "The file %s is too large (%jd MB > %zu MB): skipping",
+ filename,
+ (intmax_t)sb.st_size / 1024 / 1024,
+ max_file_size / 1024 / 1024);
+ close(fd);
+ return NULL;
+ }
+
+ f = fdopen(fd, "r");
+ if (f == NULL) {
+ SSH_LOG(SSH_LOG_RARE,
+ "Failed to open a file %s for reading: %s",
+ filename,
+ ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));
+ close(fd);
+ return NULL;
+ }
+
+ /* the flcose() will close also the underlying fd */
+ return f;
+}
+
/** @} */
diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index 9854daa..f5250b2 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -2675,6 +2675,23 @@ static void torture_config_match_complex(void **state)
ssh_string_free_char(v);
}

+/* Invalid configuration files
+ */
+static void torture_config_invalid(void **state)
+{
+ ssh_session session = *state;
+
+ ssh_options_set(session, SSH_OPTIONS_HOST, "Bar");
+
+ /* non-regular file -- ignored (or missing on non-unix) so OK */
+ _parse_config(session, "/dev/random", NULL, SSH_OK);
+
+#ifndef _WIN32
+ /* huge file -- ignored (or missing on non-unix) so OK */
+ _parse_config(session, "/proc/kcore", NULL, SSH_OK);
+#endif
+}
+
int torture_run_tests(void)
{
int rc;
@@ -2771,6 +2788,9 @@ int torture_run_tests(void)
setup, teardown),
cmocka_unit_test_setup_teardown(torture_config_match_complex,
setup, teardown),
+ cmocka_unit_test_setup_teardown(torture_config_invalid,
+ setup,
+ teardown),
};


Loading
Loading