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.10.6/include/libssh/misc.h
===================================================================
--- libssh-0.10.6.orig/include/libssh/misc.h
+++ libssh-0.10.6/include/libssh/misc.h
@@ -21,6 +21,8 @@
 #ifndef MISC_H_
 #define MISC_H_
 
+#include <stdio.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -106,6 +108,8 @@ char *ssh_strreplace(const char *src, co
 
 int ssh_check_hostname_syntax(const char *hostname);
 
+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
+
 #ifdef __cplusplus
 }
 #endif
Index: libssh-0.10.6/include/libssh/priv.h
===================================================================
--- libssh-0.10.6.orig/include/libssh/priv.h
+++ libssh-0.10.6/include/libssh/priv.h
@@ -438,6 +438,9 @@ 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
+
 #ifdef __cplusplus
 }
 #endif
Index: libssh-0.10.6/src/bind_config.c
===================================================================
--- libssh-0.10.6.orig/src/bind_config.c
+++ libssh-0.10.6/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
      * 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.10.6/src/config.c
===================================================================
--- libssh-0.10.6.orig/src/config.c
+++ libssh-0.10.6/src/config.c
@@ -215,10 +215,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;
     }
 
@@ -1205,8 +1204,9 @@ int ssh_config_parse_file(ssh_session se
     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;
     }
 
Index: libssh-0.10.6/src/dh-gex.c
===================================================================
--- libssh-0.10.6.orig/src/dh-gex.c
+++ libssh-0.10.6/src/dh-gex.c
@@ -520,9 +520,9 @@ static int ssh_retrieve_dhgroup(char *mo
     }
 
     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};
Index: libssh-0.10.6/src/known_hosts.c
===================================================================
--- libssh-0.10.6.orig/src/known_hosts.c
+++ libssh-0.10.6/src/known_hosts.c
@@ -83,7 +83,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.10.6/src/knownhosts.c
===================================================================
--- libssh-0.10.6.orig/src/knownhosts.c
+++ libssh-0.10.6/src/knownhosts.c
@@ -232,7 +232,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) {
         char err_msg[SSH_ERRNO_MSG_MAX] = {0};
         SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s",
Index: libssh-0.10.6/src/misc.c
===================================================================
--- libssh-0.10.6.orig/src/misc.c
+++ libssh-0.10.6/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
     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;
+}
+
 /** @} */
Index: libssh-0.10.6/tests/unittests/torture_config.c
===================================================================
--- libssh-0.10.6.orig/tests/unittests/torture_config.c
+++ libssh-0.10.6/tests/unittests/torture_config.c
@@ -1955,6 +1955,23 @@ static void torture_config_parse_uri(voi
     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;
@@ -2029,6 +2046,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),
     };
 
 
