From 1525ea3dda7133f1bb7fe8b5fda7b9c4e4b8304c Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 15 Jan 2026 11:32:51 +0100 Subject: [PATCH] knownhosts: Avoid memory leaks on invalid entries When `known_hosts` file contained matching valid entry followed by invalid entry, the first record was already allocated in `ssh_known_hosts_read_entries()`, but not freed on error. This could cause possible memory leaks in client, but we do not consider them as security relevant as the leaks do not add up and successful exploitaition is hard or impossible. Originally reported by Kang Yang. Signed-off-by: Jakub Jelen Reviewed-by: Norbert Pocs --- src/knownhosts.c | 80 ++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/src/knownhosts.c b/src/knownhosts.c index 7d2a505f..2807e17d 100644 --- a/src/knownhosts.c +++ b/src/knownhosts.c @@ -216,11 +216,22 @@ ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, return 0; } -/* This method reads the known_hosts file referenced by the path +/** + * @internal + * + * @brief Read entries from filename to provided list + * + * This method reads the known_hosts file referenced by the path * in filename argument, and entries matching the match argument * will be added to the list in entries argument. * If the entries list is NULL, it will allocate a new list. Caller * is responsible to free it even if an error occurs. + * + * @param match[in] The host name (with port) to match against + * @param filename[in] The known hosts file to parse + * @param entries[in,out] The list of entries to append matching ones + * @return `SSH_OK` on missing file or success parsing, + * `SSH_ERROR` on error */ static int ssh_known_hosts_read_entries(const char *match, const char *filename, @@ -346,6 +357,33 @@ static char *ssh_session_get_host_port(ssh_session session) /** * @internal + * + * @brief Free known hosts entries list + * + * @param[in] entry_list The list of ssh_knownhosts_entry items + */ +static void ssh_knownhosts_entries_free(struct ssh_list *entry_list) +{ + struct ssh_iterator *it = NULL; + + if (entry_list == NULL) { + return; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); +} +/** + * @internal + * * @brief Check which host keys should be preferred for the session. * * This checks the known_hosts file to find out which algorithms should be @@ -453,7 +491,7 @@ struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) return list; error: - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); ssh_list_free(list); return NULL; } @@ -505,6 +543,7 @@ static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e typ /** * @internal + * * @brief Get the host keys algorithms identifiers from the known_hosts files * * This expands the signatures types that can be generated from the keys types @@ -549,7 +588,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -558,7 +597,7 @@ char *ssh_known_hosts_get_algorithms_names(ssh_session session) &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -799,7 +838,6 @@ out: enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) { struct ssh_list *entry_list = NULL; - struct ssh_iterator *it = NULL; char *host_port = NULL; bool global_known_hosts_found = false; bool known_hosts_found = false; @@ -860,7 +898,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -871,7 +909,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -883,16 +921,7 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) return SSH_KNOWN_HOSTS_UNKNOWN; } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_OK; } @@ -1079,13 +1108,13 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, filename, &entry_list); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } it = ssh_list_get_iterator(entry_list); if (it == NULL) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } @@ -1115,16 +1144,7 @@ ssh_known_hosts_check_server_key(const char *hosts_entry, } } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return found; } @@ -1196,6 +1216,8 @@ ssh_session_get_known_hosts_entry(ssh_session session, } /** + * @internal + * * @brief Get the known_hosts entry for the current connected session * from the given known_hosts file. *