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 a5eb30dbfd)
This commit is contained in:
Jakub Jelen
2025-12-11 17:33:19 +01:00
parent 3e1d276a5a
commit bf390a0426
9 changed files with 110 additions and 10 deletions

View File

@@ -36,6 +36,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <stdbool.h> #include <stdbool.h>
#endif /* _WIN32 */ #endif /* _WIN32 */
#include <stdio.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -136,6 +137,8 @@ int ssh_check_username_syntax(const char *username);
void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list);
bool ssh_libssh_proxy_jumps(void); bool ssh_libssh_proxy_jumps(void);
FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -473,6 +473,9 @@ char *ssh_strerror(int err_num, char *buf, size_t buflen);
#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) #define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1)
int encode_current_tty_opts(unsigned char *buf, size_t buflen); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind,
return; return;
} }
f = fopen(filename, "r"); f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) { if (f == NULL) {
SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load",
filename); 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. */ * option to be redefined later by another file. */
uint8_t seen[BIND_CFG_MAX] = {0}; uint8_t seen[BIND_CFG_MAX] = {0};
f = fopen(filename, "r"); f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) { if (f == NULL) {
return 0; return 0;
} }

View File

@@ -223,10 +223,9 @@ local_parse_file(ssh_session session,
return; return;
} }
f = fopen(filename, "r"); f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) { if (f == NULL) {
SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", /* The underlying function logs the reasons */
filename);
return; return;
} }
@@ -1464,8 +1463,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
int parsing, rv; int parsing, rv;
bool global = 0; bool global = 0;
f = fopen(filename, "r"); f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) { if (f == NULL) {
/* The underlying function logs the reasons */
return 0; return 0;
} }

View File

@@ -519,9 +519,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file,
} }
if (moduli_file != NULL) if (moduli_file != NULL)
moduli = fopen(moduli_file, "r"); moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE);
else else
moduli = fopen(MODULI_FILE, "r"); moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE);
if (moduli == NULL) { if (moduli == NULL) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0}; char err_msg[SSH_ERRNO_MSG_MAX] = {0};

View File

@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
struct ssh_tokens_st *tokens = NULL; struct ssh_tokens_st *tokens = NULL;
if (*file == NULL) { if (*file == NULL) {
*file = fopen(filename,"r"); *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (*file == NULL) { if (*file == NULL) {
return NULL; return NULL;
} }

View File

@@ -243,7 +243,7 @@ static int ssh_known_hosts_read_entries(const char *match,
FILE *fp = NULL; FILE *fp = NULL;
int rc; int rc;
fp = fopen(filename, "r"); fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (fp == NULL) { if (fp == NULL) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0}; char err_msg[SSH_ERRNO_MSG_MAX] = {0};
SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s",

View File

@@ -37,6 +37,7 @@
#endif /* _WIN32 */ #endif /* _WIN32 */
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -2244,4 +2245,77 @@ ssh_libssh_proxy_jumps(void)
return !(t != NULL && t[0] == '1'); 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;
}
/** @} */ /** @} */

View File

@@ -2811,6 +2811,23 @@ static void torture_config_jump(void **state)
printf("%s: EOF\n", __func__); printf("%s: EOF\n", __func__);
} }
/* 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 torture_run_tests(void)
{ {
int rc; int rc;
@@ -2913,6 +2930,9 @@ int torture_run_tests(void)
cmocka_unit_test_setup_teardown(torture_config_jump, cmocka_unit_test_setup_teardown(torture_config_jump,
setup, setup,
teardown), teardown),
cmocka_unit_test_setup_teardown(torture_config_invalid,
setup,
teardown),
}; };