File libssh-CVE-2026-0965-config-Do-not-attempt-to-read-non-regu.patch of Package libssh.42763
From bf390a042623e02abc8f421c4c5fadc0429a8a76 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Thu, 11 Dec 2025 17:33:19 +0100
Subject: [PATCH 08/12] 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)
Index: libssh-0.9.8/include/libssh/misc.h
===================================================================
--- libssh-0.9.8.orig/include/libssh/misc.h
+++ libssh-0.9.8/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);
@@ -99,4 +101,6 @@ int ssh_newline_vis(const char *string,
int ssh_check_hostname_syntax(const char *hostname);
+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
+
#endif /* MISC_H_ */
Index: libssh-0.9.8/include/libssh/priv.h
===================================================================
--- libssh-0.9.8.orig/include/libssh/priv.h
+++ libssh-0.9.8/include/libssh/priv.h
@@ -426,4 +426,10 @@ void ssh_agent_state_free(void *data);
bool is_ssh_initialized(void);
+char *ssh_strerror(int err_num, char *buf, size_t buflen);
+#define SSH_ERRNO_MSG_MAX 1024
+
+/** The default maximum file size for a configuration file */
+#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024
+
#endif /* _LIBSSH_PRIV_H */
Index: libssh-0.9.8/src/bind_config.c
===================================================================
--- libssh-0.9.8.orig/src/bind_config.c
+++ libssh-0.9.8/src/bind_config.c
@@ -199,7 +199,7 @@ static void local_parse_file(ssh_bind bi
unsigned int count = 0;
int rv;
- 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);
@@ -616,7 +616,7 @@ int ssh_bind_config_parse_file(ssh_bind
* 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;
}
Index: libssh-0.9.8/src/config.c
===================================================================
--- libssh-0.9.8.orig/src/config.c
+++ libssh-0.9.8/src/config.c
@@ -205,10 +205,9 @@ local_parse_file(ssh_session session,
unsigned int count = 0;
int rv;
- 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;
}
@@ -1019,8 +1018,9 @@ int ssh_config_parse_file(ssh_session se
FILE *f = NULL;
int parsing, rv;
- 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;
}
Index: libssh-0.9.8/src/dh-gex.c
===================================================================
--- libssh-0.9.8.orig/src/dh-gex.c
+++ libssh-0.9.8/src/dh-gex.c
@@ -508,7 +508,7 @@ static int ssh_retrieve_dhgroup(uint32_t
return ssh_fallback_group(pmax, p, g);
}
- moduli = fopen(MODULI_FILE, "r");
+ moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE);
if (moduli == NULL) {
SSH_LOG(SSH_LOG_WARNING,
"Unable to open moduli file: %s",
Index: libssh-0.9.8/src/known_hosts.c
===================================================================
--- libssh-0.9.8.orig/src/known_hosts.c
+++ libssh-0.9.8/src/known_hosts.c
@@ -79,7 +79,7 @@ static struct ssh_tokens_st *ssh_get_kno
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;
}
Index: libssh-0.9.8/src/knownhosts.c
===================================================================
--- libssh-0.9.8.orig/src/knownhosts.c
+++ libssh-0.9.8/src/knownhosts.c
@@ -222,7 +222,7 @@ static int ssh_known_hosts_read_entries(
FILE *fp = NULL;
int rc;
- fp = fopen(filename, "r");
+ fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (fp == NULL) {
SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s",
filename, strerror(errno));
Index: libssh-0.9.8/src/misc.c
===================================================================
--- libssh-0.9.8.orig/src/misc.c
+++ libssh-0.9.8/src/misc.c
@@ -37,6 +37,7 @@
#endif /* _WIN32 */
#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
@@ -1834,4 +1835,101 @@ int ssh_check_hostname_syntax(const char
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_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;
+}
+
+/**
+ * @internal
+ *
+ * @brief Processes errno into error string
+ *
+ * @param[in] err_num The errno value
+ * @param[out] buf Pointer to a place where the string could be saved
+ * @param[in] buflen The allocated size of buf
+ *
+ * @return error string
+ */
+char *ssh_strerror(int err_num, char *buf, size_t buflen)
+{
+#if defined(_WIN32)
+ strerror_s(buf, buflen, err_num);
+ return buf;
+#elif !defined(_GNU_SOURCE)
+ strerror_r(err_num, buf, buflen);
+ return buf;
+#else
+ return strerror_r(err_num, buf, buflen);
+#endif /* _WIN32 */
+}
+
/** @} */