Resolves: RHEL-150661 Resolves: CVE-2025-4877 Resolves: CVE-2025-4878 Resolves: CVE-2025-5351 Resolves: CVE-2025-8114 Resolves: CVE-2025-8277 Resolves: CVE-2026-0964 Resolves: CVE-2026-0965 Resolves: CVE-2026-0966 Resolves: CVE-2026-0967 Resolves: CVE-2026-0968
274 lines
8.5 KiB
Diff
274 lines
8.5 KiB
Diff
From 63079a26261b5ea6736fcdf68077135e7f2c991d Mon Sep 17 00:00:00 2001
|
|
From: Jakub Jelen <jjelen@redhat.com>
|
|
Date: Thu, 11 Dec 2025 17:33:19 +0100
|
|
Subject: [PATCH] CVE-2026-0965 config: Do not attempt to read non-regular and
|
|
too large configuration files
|
|
|
|
Changes also the reading of known_hosts to use the new helper function
|
|
|
|
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
|
|
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
|
|
(cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798)
|
|
---
|
|
include/libssh/misc.h | 4 ++
|
|
include/libssh/priv.h | 3 ++
|
|
src/bind_config.c | 4 +-
|
|
src/config.c | 5 ++-
|
|
src/dh-gex.c | 4 +-
|
|
src/known_hosts.c | 2 +-
|
|
src/knownhosts.c | 2 +-
|
|
src/misc.c | 74 ++++++++++++++++++++++++++++++++
|
|
tests/unittests/torture_config.c | 19 ++++++++
|
|
9 files changed, 109 insertions(+), 8 deletions(-)
|
|
|
|
diff --git a/include/libssh/misc.h b/include/libssh/misc.h
|
|
index 47a423f2..3452251d 100644
|
|
--- a/include/libssh/misc.h
|
|
+++ b/include/libssh/misc.h
|
|
@@ -21,6 +21,8 @@
|
|
#ifndef MISC_H_
|
|
#define MISC_H_
|
|
|
|
+#include <stdio.h>
|
|
+
|
|
/* in misc.c */
|
|
/* gets the user home dir. */
|
|
char *ssh_get_user_home_dir(void);
|
|
@@ -102,4 +104,6 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *repl);
|
|
|
|
int ssh_check_hostname_syntax(const char *hostname);
|
|
|
|
+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
|
|
+
|
|
#endif /* MISC_H_ */
|
|
diff --git a/include/libssh/priv.h b/include/libssh/priv.h
|
|
index bab761b0..876ea4ed 100644
|
|
--- a/include/libssh/priv.h
|
|
+++ b/include/libssh/priv.h
|
|
@@ -434,4 +434,7 @@ bool is_ssh_initialized(void);
|
|
#define SSH_ERRNO_MSG_MAX 1024
|
|
char *ssh_strerror(int err_num, char *buf, size_t buflen);
|
|
|
|
+/** The default maximum file size for a configuration file */
|
|
+#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024
|
|
+
|
|
#endif /* _LIBSSH_PRIV_H */
|
|
diff --git a/src/bind_config.c b/src/bind_config.c
|
|
index 9e4a7fd4..c12f1003 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 94b70d58..bf349812 100644
|
|
--- a/src/config.c
|
|
+++ b/src/config.c
|
|
@@ -215,7 +215,7 @@ 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);
|
|
@@ -1205,8 +1205,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 b022f09e..81219586 100644
|
|
--- a/src/dh-gex.c
|
|
+++ b/src/dh-gex.c
|
|
@@ -520,9 +520,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 f660a6f3..ba2ae4d5 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 62777ad2..e4865ac2 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 7bb431b6..6607775e 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>
|
|
@@ -2074,4 +2075,77 @@ int ssh_check_hostname_syntax(const char *hostname)
|
|
return SSH_OK;
|
|
}
|
|
|
|
+/**
|
|
+ * @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_TRACE,
|
|
+ "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_TRACE,
|
|
+ "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_TRACE,
|
|
+ "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_TRACE,
|
|
+ "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_TRACE,
|
|
+ "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 995a5213..70ecfb95 100644
|
|
--- a/tests/unittests/torture_config.c
|
|
+++ b/tests/unittests/torture_config.c
|
|
@@ -2237,6 +2237,23 @@ static void torture_config_parse_uri(void **state)
|
|
SAFE_FREE(hostname);
|
|
}
|
|
|
|
+/* 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;
|
|
@@ -2323,6 +2340,8 @@ int torture_run_tests(void)
|
|
setup_no_sshdir, teardown),
|
|
cmocka_unit_test_setup_teardown(torture_config_parse_uri,
|
|
setup, teardown),
|
|
+ cmocka_unit_test_setup_teardown(torture_config_invalid,
|
|
+ setup, teardown),
|
|
};
|
|
|
|
|
|
--
|
|
2.53.0
|
|
|