Backport of:

From e8bdfb151a356d0171fea4194dd205fbb252be23 Mon Sep 17 00:00:00 2001
From: "djm@openbsd.org" <djm@openbsd.org>
Date: Mon, 6 Jul 2026 07:49:58 +0000
Subject: [PATCH] upstream: fix ownership and lifetime of several bits of
 client

state that need to persist for the life of the connection, especially the
cached hostkey that was being incorrectly freed early on some paths, possibly
allowing its use after free.

Reported by Zhenpeng (Leo) Lin from depthfirst.com

OpenBSD-Commit-ID: faaa6ad72e7d69d41fa8b197b606265b7d9bc73f
---
 ssh.c         | 26 +++-----------------------
 sshconnect.c  | 49 ++++++++++++++++++++++++++++++++++++++++++++++---
 sshconnect.h  |  9 ++++++---
 sshconnect2.c | 22 ++++++++++++----------
 4 files changed, 67 insertions(+), 39 deletions(-)

--- a/ssh.c
+++ b/ssh.c
@@ -592,25 +592,6 @@ set_addrinfo_port(struct addrinfo *addrs
 	}
 }
 
-static void
-ssh_conn_info_free(struct ssh_conn_info *cinfo)
-{
-	if (cinfo == NULL)
-		return;
-	free(cinfo->conn_hash_hex);
-	free(cinfo->shorthost);
-	free(cinfo->uidstr);
-	free(cinfo->keyalias);
-	free(cinfo->thishost);
-	free(cinfo->host_arg);
-	free(cinfo->portstr);
-	free(cinfo->remhost);
-	free(cinfo->remuser);
-	free(cinfo->homedir);
-	free(cinfo->locuser);
-	free(cinfo);
-}
-
 /*
  * Main program for the ssh client.
  */
@@ -1670,8 +1651,8 @@ main(int ac, char **av)
 	ssh_signal(SIGCHLD, main_sigchld_handler);
 
 	/* Log into the remote system.  Never returns if the login fails. */
-	ssh_login(ssh, &sensitive_data, host, (struct sockaddr *)&hostaddr,
-	    options.port, pw, timeout_ms, cinfo);
+	ssh_login(ssh, &sensitive_data, host, &hostaddr, options.port,
+	    pw, timeout_ms, cinfo);
 
 	/* We no longer need the private host keys.  Clear them now. */
 	if (sensitive_data.nkeys != 0) {
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -84,6 +84,47 @@ extern char *__progname;
 static int show_other_keys(struct hostkeys *, struct sshkey *);
 static void warn_changed_key(struct sshkey *);
 
+void
+ssh_conn_info_free(struct ssh_conn_info *cinfo)
+{
+	if (cinfo == NULL)
+		return;
+	free(cinfo->conn_hash_hex);
+	free(cinfo->shorthost);
+	free(cinfo->uidstr);
+	free(cinfo->keyalias);
+	free(cinfo->thishost);
+	free(cinfo->host_arg);
+	free(cinfo->portstr);
+	free(cinfo->remhost);
+	free(cinfo->remuser);
+	free(cinfo->homedir);
+	free(cinfo->locuser);
+	freezero(cinfo, sizeof(*cinfo));
+}
+
+struct ssh_conn_info *
+ssh_conn_info_dup(const struct ssh_conn_info *cinfo)
+{
+	struct ssh_conn_info *ret;
+
+	if (cinfo == NULL)
+		return NULL;
+	ret = xcalloc(1, sizeof(*ret));
+	ret->conn_hash_hex = xstrdup(cinfo->conn_hash_hex);
+	ret->shorthost = xstrdup(cinfo->shorthost);
+	ret->uidstr = xstrdup(cinfo->uidstr);
+	ret->keyalias = xstrdup(cinfo->keyalias);
+	ret->thishost = xstrdup(cinfo->thishost);
+	ret->host_arg = xstrdup(cinfo->host_arg);
+	ret->portstr = xstrdup(cinfo->portstr);
+	ret->remhost = xstrdup(cinfo->remhost);
+	ret->remuser = xstrdup(cinfo->remuser);
+	ret->homedir = xstrdup(cinfo->homedir);
+	ret->locuser = xstrdup(cinfo->locuser);
+	return ret;
+}
+
 /* Expand a proxy command */
 static char *
 expand_proxy_command(const char *proxy_command, const char *user,
@@ -1545,8 +1586,8 @@ out:
  */
 void
 ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost,
-    struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms,
-    const struct ssh_conn_info *cinfo)
+    struct sockaddr_storage *hostaddr, u_short port, struct passwd *pw,
+    int timeout_ms, const struct ssh_conn_info *cinfo)
 {
 	char *host;
 	char *server_user, *local_user;
--- a/sshconnect.h
+++ b/sshconnect.h
@@ -69,7 +69,7 @@ int	 ssh_connect(struct ssh *, const cha
 void	 ssh_kill_proxy_command(void);
 
 void	 ssh_login(struct ssh *, Sensitive *, const char *,
-    struct sockaddr *, u_short, struct passwd *, int,
+    struct sockaddr_storage *, u_short, struct passwd *, int,
     const struct ssh_conn_info *);
 
 int	 verify_host_key(char *, struct sockaddr *, struct sshkey *,
@@ -78,7 +78,7 @@ int	 verify_host_key(char *, struct sock
 void	 get_hostfile_hostname_ipaddr(char *, struct sockaddr *, u_short,
     char **, char **);
 
-void	 ssh_kex2(struct ssh *ssh, char *, struct sockaddr *, u_short,
+void	 ssh_kex2(struct ssh *ssh, char *, struct sockaddr_storage *, u_short,
     const struct ssh_conn_info *);
 
 void	 ssh_userauth2(struct ssh *ssh, const char *, const char *,
@@ -92,3 +92,6 @@ void	 maybe_add_key_to_agent(const char
 void	 load_hostkeys_command(struct hostkeys *, const char *,
     const char *, const struct ssh_conn_info *,
     const struct sshkey *, const char *);
+
+void ssh_conn_info_free(struct ssh_conn_info *);
+struct ssh_conn_info *ssh_conn_info_dup(const struct ssh_conn_info *);
--- a/sshconnect2.c
+++ b/sshconnect2.c
@@ -88,7 +88,7 @@ extern Options options;
  */
 
 static char *xxx_host;
-static struct sockaddr *xxx_hostaddr;
+static struct sockaddr_storage xxx_hostaddr;
 static const struct ssh_conn_info *xxx_conn_info;
 static int key_type_allowed(struct sshkey *, const char *);
 
@@ -99,7 +99,7 @@ verify_host_key_callback(struct sshkey *
 		fatal("Server host key %s not in HostKeyAlgorithms",
 		    sshkey_ssh_name(hostkey));
 	}
-	if (verify_host_key(xxx_host, xxx_hostaddr, hostkey,
+	if (verify_host_key(xxx_host, (struct sockaddr *)&xxx_hostaddr, hostkey,
 	    xxx_conn_info) != 0)
 		fatal("Host key verification failed.");
 	return 0;
@@ -216,8 +216,8 @@ order_hostkeyalgs(char *host, struct soc
 }
 
 void
-ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port,
-    const struct ssh_conn_info *cinfo)
+ssh_kex2(struct ssh *ssh, char *host, struct sockaddr_storage *hostaddr,
+    u_short port, const struct ssh_conn_info *cinfo)
 {
 	char *myproposal[PROPOSAL_MAX] = { KEX_CLIENT };
 	char *s, *all_key;
@@ -228,9 +228,9 @@ ssh_kex2(struct ssh *ssh, char *host, st
 	char *gss_host = NULL;
 #endif
 
-	xxx_host = host;
-	xxx_hostaddr = hostaddr;
-	xxx_conn_info = cinfo;
+	xxx_host = xstrdup(host);
+	xxx_hostaddr = *hostaddr;
+	xxx_conn_info = ssh_conn_info_dup(cinfo);
 
 	/*
 	 * If the user has not specified HostkeyAlgorithms, or has only
@@ -266,7 +266,7 @@ ssh_kex2(struct ssh *ssh, char *host, st
 		/* Query known_hosts and prefer algorithms that appear there */
 		myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
 		    compat_pkalg_proposal(ssh,
-		    order_hostkeyalgs(host, hostaddr, port, cinfo));
+		    order_hostkeyalgs(host, (struct sockaddr *)hostaddr, port, cinfo));
 	} else {
 		/* Use specified HostkeyAlgorithms exactly */
 		myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
