File openssh-mitigate-lingering-secrets.patch of Package openssh-askpass-gnome.32612

From ebd24c765804ad0b70426f7298d7f6c8daa7038b Mon Sep 17 00:00:00 2001
From: Hans Petter Jansson <hpj@hpjansson.org>
Date: Mon, 7 Jun 2021 22:12:05 +0200
Subject: [PATCH] Add mitigations for secrets potentially lingering in memory

---
 kex.c     |  8 ++++----
 mac.c     |  4 ++--
 monitor.c |  8 ++++++--
 packet.c  | 49 +++++++++++++++++++++++++++++++++++++++----------
 packet.h  |  1 +
 sshbuf.c  | 25 +++++++++++++++++++++++++
 sshbuf.h  |  3 +++
 sshd.c    | 25 ++++++++++++++++++++++++-
 8 files changed, 104 insertions(+), 19 deletions(-)

Index: openssh-8.4p1/kex.c
===================================================================
--- openssh-8.4p1.orig/kex.c
+++ openssh-8.4p1/kex.c
@@ -1398,16 +1398,16 @@ enc_destroy(struct sshenc *enc)
 		return;
 
 	if (enc->key) {
-		memset(enc->key, 0, enc->key_len);
+		explicit_bzero(enc->key, enc->key_len);
 		free(enc->key);
 	}
 
 	if (enc->iv) {
-		memset(enc->iv,  0, enc->iv_len);
+		explicit_bzero(enc->iv,  enc->iv_len);
 		free(enc->iv);
 	}
 
-	memset(enc, 0, sizeof(*enc));
+	explicit_bzero(enc, sizeof(*enc));
 }
 
 void
@@ -1418,7 +1418,7 @@ newkeys_destroy(struct newkeys *newkeys)
 
 	enc_destroy(&newkeys->enc);
 	mac_destroy(&newkeys->mac);
-	memset(&newkeys->comp, 0, sizeof(newkeys->comp));
+	explicit_bzero(&newkeys->comp, sizeof(newkeys->comp));
 }
 
 /*
Index: openssh-8.4p1/mac.c
===================================================================
--- openssh-8.4p1.orig/mac.c
+++ openssh-8.4p1/mac.c
@@ -284,11 +284,11 @@ mac_destroy(struct sshmac *mac)
 		return;
 
 	if (mac->key) {
-		memset(mac->key, 0, mac->key_len);
+		explicit_bzero(mac->key, mac->key_len);
 		free(mac->key);
 	}
 
-	memset(mac, 0, sizeof(*mac));
+	explicit_bzero(mac, sizeof(*mac));
 }
 
 /* XXX copied from ciphers_valid */
Index: openssh-8.4p1/monitor.c
===================================================================
--- openssh-8.4p1.orig/monitor.c
+++ openssh-8.4p1/monitor.c
@@ -1778,8 +1778,12 @@ mm_answer_audit_end_command(struct ssh *
 void
 monitor_clear_keystate(struct ssh *ssh, struct monitor *pmonitor)
 {
-	ssh_clear_newkeys(ssh, MODE_IN);
-	ssh_clear_newkeys(ssh, MODE_OUT);
+	u_int mode;
+
+	for (mode = 0; mode < MODE_MAX; mode++) {
+		ssh_clear_curkeys(ssh, mode);  /* current keys */
+		ssh_clear_newkeys(ssh, mode);  /* next keys */
+	}
 	sshbuf_free(child_state);
 	child_state = NULL;
 }
Index: openssh-8.4p1/packet.c
===================================================================
--- openssh-8.4p1.orig/packet.c
+++ openssh-8.4p1/packet.c
@@ -667,6 +667,7 @@ ssh_packet_close_internal(struct ssh *ss
 		ssh->local_ipaddr = NULL;
 		free(ssh->remote_ipaddr);
 		ssh->remote_ipaddr = NULL;
+		explicit_bzero(ssh->state, sizeof(*ssh->state));
 		free(ssh->state);
 		ssh->state = NULL;
 		kex_free(ssh->kex);
@@ -795,8 +796,10 @@ compress_buffer(struct ssh *ssh, struct
 		case Z_OK:
 			/* Append compressed data to output_buffer. */
 			if ((r = sshbuf_put(out, buf, sizeof(buf) -
-			    ssh->state->compression_out_stream.avail_out)) != 0)
+			    ssh->state->compression_out_stream.avail_out)) != 0) {
+				explicit_bzero(buf, sizeof(buf));
 				return r;
+			}
 			break;
 		case Z_STREAM_ERROR:
 		default:
@@ -831,8 +834,10 @@ uncompress_buffer(struct ssh *ssh, struc
 		switch (status) {
 		case Z_OK:
 			if ((r = sshbuf_put(out, buf, sizeof(buf) -
-			    ssh->state->compression_in_stream.avail_out)) != 0)
+			    ssh->state->compression_in_stream.avail_out)) != 0) {
+				explicit_bzero(buf, sizeof(buf));
 				return r;
+			}
 			break;
 		case Z_BUF_ERROR:
 			/*
@@ -882,6 +887,17 @@ uncompress_buffer(struct ssh *ssh, struc
 #endif	/* WITH_ZLIB */
 
 void
+ssh_clear_curkeys(struct ssh *ssh, int mode)
+{
+	struct session_state *state = ssh->state;
+
+	if (state && state->newkeys[mode]) {
+		kex_free_newkeys(state->newkeys[mode]);
+		state->newkeys[mode] = NULL;
+	}
+}
+
+void
 ssh_clear_newkeys(struct ssh *ssh, int mode)
 {
 	if (ssh->kex && ssh->kex->newkeys[mode]) {
@@ -1432,6 +1448,7 @@ ssh_packet_read_seqnr(struct ssh *ssh, u
 			goto out;
 	}
  out:
+	explicit_bzero(buf, sizeof (buf));
 	free(setp);
 	return r;
 }
@@ -2403,9 +2420,12 @@ ssh_packet_get_state(struct ssh *ssh, st
 	    (r = sshbuf_put_u32(m, state->p_read.packets)) != 0 ||
 	    (r = sshbuf_put_u64(m, state->p_read.bytes)) != 0 ||
 	    (r = sshbuf_put_stringb(m, state->input)) != 0 ||
-	    (r = sshbuf_put_stringb(m, state->output)) != 0)
+	    (r = sshbuf_put_stringb(m, state->output)) != 0) {
+		sshbuf_obfuscate(m);
 		return r;
+	}
 
+	sshbuf_obfuscate(m);
 	return 0;
 }
 
@@ -2524,6 +2544,8 @@ ssh_packet_set_state(struct ssh *ssh, st
 	size_t ilen, olen;
 	int r;
 
+	sshbuf_unobfuscate(m);
+
 	if ((r = kex_from_blob(m, &ssh->kex)) != 0 ||
 	    (r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 ||
 	    (r = newkeys_from_blob(m, ssh, MODE_IN)) != 0 ||
@@ -2537,7 +2559,7 @@ ssh_packet_set_state(struct ssh *ssh, st
 	    (r = sshbuf_get_u64(m, &state->p_read.blocks)) != 0 ||
 	    (r = sshbuf_get_u32(m, &state->p_read.packets)) != 0 ||
 	    (r = sshbuf_get_u64(m, &state->p_read.bytes)) != 0)
-		return r;
+		goto out;
 	/*
 	 * We set the time here so that in post-auth privsep child we
 	 * count from the completion of the authentication.
@@ -2546,10 +2568,10 @@ ssh_packet_set_state(struct ssh *ssh, st
 	/* XXX ssh_set_newkeys overrides p_read.packets? XXX */
 	if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0 ||
 	    (r = ssh_set_newkeys(ssh, MODE_OUT)) != 0)
-		return r;
+		goto out;
 
 	if ((r = ssh_packet_set_postauth(ssh)) != 0)
-		return r;
+		goto out;
 
 	sshbuf_reset(state->input);
 	sshbuf_reset(state->output);
@@ -2557,12 +2579,19 @@ ssh_packet_set_state(struct ssh *ssh, st
 	    (r = sshbuf_get_string_direct(m, &output, &olen)) != 0 ||
 	    (r = sshbuf_put(state->input, input, ilen)) != 0 ||
 	    (r = sshbuf_put(state->output, output, olen)) != 0)
-		return r;
+		goto out;
 
-	if (sshbuf_len(m))
-		return SSH_ERR_INVALID_FORMAT;
+	if (sshbuf_len(m)) {
+		r = SSH_ERR_INVALID_FORMAT;
+		goto out;
+	}
 	debug3("%s: done", __func__);
-	return 0;
+
+	r = 0;
+out:
+	if (r != 0)
+		sshbuf_obfuscate(m);
+	return r;
 }
 
 /* NEW API */
Index: openssh-8.4p1/packet.h
===================================================================
--- openssh-8.4p1.orig/packet.h
+++ openssh-8.4p1/packet.h
@@ -103,6 +103,7 @@ void     ssh_packet_close(struct ssh *);
 void	 ssh_packet_set_input_hook(struct ssh *, ssh_packet_hook_fn *, void *);
 void	 ssh_packet_clear_keys(struct ssh *);
 void     ssh_packet_clear_keys_noaudit(struct ssh *);
+void	 ssh_clear_curkeys(struct ssh *, int);
 void	 ssh_clear_newkeys(struct ssh *, int);
 
 int	 ssh_packet_is_rekeying(struct ssh *);
Index: openssh-8.4p1/sshbuf.c
===================================================================
--- openssh-8.4p1.orig/sshbuf.c
+++ openssh-8.4p1/sshbuf.c
@@ -283,6 +283,31 @@ sshbuf_mutable_ptr(const struct sshbuf *
 	return buf->d + buf->off;
 }
 
+/* Trivially obfuscate the buffer. This is used to make sensitive data
+ * (e.g. keystate) slightly less obvious if found lingering in kernel
+ * memory after being sent from the privsep child to its parent.
+ *
+ * Longer term we should consider using a one-time pad or a stream cipher
+ * here. */
+void
+sshbuf_obfuscate(struct sshbuf *buf)
+{
+	size_t i;
+
+	if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
+		return;
+
+	for (i = buf->off; i < buf->size; i++) {
+		buf->d [i] ^= 0xaa;
+	}
+}
+
+void
+sshbuf_unobfuscate(struct sshbuf *buf)
+{
+	sshbuf_obfuscate(buf);
+}
+
 int
 sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
 {
Index: openssh-8.4p1/sshbuf.h
===================================================================
--- openssh-8.4p1.orig/sshbuf.h
+++ openssh-8.4p1/sshbuf.h
@@ -309,6 +309,9 @@ int sshbuf_load_file(const char *, struc
 int sshbuf_write_file(const char *path, struct sshbuf *buf)
     __attribute__((__nonnull__ (2)));
 
+void sshbuf_obfuscate(struct sshbuf *buf);
+void sshbuf_unobfuscate(struct sshbuf *buf);
+
 /* Macros for decoding/encoding integers */
 #define PEEK_U64(p) \
 	(((u_int64_t)(((const u_char *)(p))[0]) << 56) | \
Index: openssh-8.4p1/sshd.c
===================================================================
--- openssh-8.4p1.orig/sshd.c
+++ openssh-8.4p1/sshd.c
@@ -275,6 +275,19 @@ static void do_ssh2_kex(struct ssh *);
 static char *listener_proctitle;
 
 /*
+ * Clear some stack space. This is a bit naive, but hopefully helps mitigate
+ * information leaks due to registers and other data having been stored on
+ * the stack. Called after fork() and before exit().
+ */
+static void
+clobber_stack(void)
+{
+	char data [32768];
+
+	explicit_bzero(data, 32768);
+}
+
+/*
  * Close all listening sockets
  */
 static void
@@ -443,6 +456,8 @@ destroy_sensitive_data(struct ssh *ssh,
 			sensitive_data.host_certificates[i] = NULL;
 		}
 	}
+
+	clobber_stack();
 }
 
 /* Demote private to public keys for network child */
@@ -615,6 +630,8 @@ privsep_preauth(struct ssh *ssh)
 static void
 privsep_postauth(struct ssh *ssh, Authctxt *authctxt)
 {
+	clobber_stack();
+
 #ifdef DISABLE_FD_PASSING
 	if (1) {
 #else
@@ -2328,6 +2345,7 @@ main(int ac, char **av)
 	if (use_privsep) {
 		mm_send_keystate(ssh, pmonitor);
 		ssh_packet_clear_keys(ssh);
+		clobber_stack();
 		exit(0);
 	}
 
@@ -2404,6 +2422,7 @@ main(int ac, char **av)
 	if (use_privsep)
 		mm_terminate();
 
+	clobber_stack();
 	exit(0);
 }
 
@@ -2574,8 +2593,10 @@ cleanup_exit(int i)
 	/* cleanup_exit can be called at the very least from the privsep
 	   wrappers used for auditing.  Make sure we don't recurse
 	   indefinitely. */
-	if (in_cleanup)
+	if (in_cleanup) {
+		clobber_stack();
 		_exit(i);
+	}
 	in_cleanup = 1;
 	if (the_active_state != NULL && the_authctxt != NULL) {
 		do_cleanup(the_active_state, the_authctxt);
@@ -2600,5 +2621,7 @@ cleanup_exit(int i)
 	    (!use_privsep || mm_is_monitor()))
 		audit_event(the_active_state, SSH_CONNECTION_ABANDON);
 #endif
+
+	clobber_stack();
 	_exit(i);
 }
openSUSE Build Service is sponsored by