Antonio Larrosa
da2c6cc517
* No changes for askpass, see main package changelog for details. - Fix a dbus connection leaked in the logind patch that was missing a sd_bus_unref call (found by Matthias Gerstner): * logind_set_tty.patch - Add a patch that fixes a small memory leak when parsing the subsystem configuration option: * fix-memleak-in-process_server_config_line_depth.patch - Update to openssh 9.8p1: = Security * 1) Race condition in sshd(8) (bsc#1226642, CVE-2024-6387). A critical vulnerability in sshd(8) was present in Portable OpenSSH versions between 8.5p1 and 9.7p1 (inclusive) that may allow arbitrary code execution with root privileges. Successful exploitation has been demonstrated on 32-bit Linux/glibc systems with ASLR. Under lab conditions, the attack requires on average 6-8 hours of continuous connections up to the maximum the server will accept. Exploitation on 64-bit systems is believed to be possible but has not been demonstrated at this time. It's likely that these attacks will be improved upon. Exploitation on non-glibc systems is conceivable but has not been examined. Systems that lack ASLR or users of downstream Linux distributions that have modified OpenSSH to disable per-connection ASLR re-randomisation (yes - this is a thing, no - we don't understand why) may potentially have an easier path to exploitation. OpenBSD is not vulnerable. OBS-URL: https://build.opensuse.org/package/show/network/openssh?expand=0&rev=272
345 lines
9.5 KiB
Diff
345 lines
9.5 KiB
Diff
Index: openssh-9.3p2/kex.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/kex.c
|
|
+++ openssh-9.3p2/kex.c
|
|
@@ -1564,16 +1564,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
|
|
@@ -1584,7 +1584,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-9.3p2/mac.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/mac.c
|
|
+++ openssh-9.3p2/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-9.3p2/monitor.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/monitor.c
|
|
+++ openssh-9.3p2/monitor.c
|
|
@@ -1789,8 +1789,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-9.3p2/packet.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/packet.c
|
|
+++ openssh-9.3p2/packet.c
|
|
@@ -655,6 +655,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);
|
|
@@ -783,8 +784,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:
|
|
@@ -819,8 +822,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:
|
|
/*
|
|
@@ -870,6 +875,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]) {
|
|
@@ -1418,7 +1434,9 @@ ssh_packet_read_seqnr(struct ssh *ssh, u
|
|
}
|
|
|
|
/* Append it to the buffer. */
|
|
- if ((r = ssh_packet_process_incoming(ssh, buf, len)) != 0)
|
|
+ r = ssh_packet_process_incoming(ssh, buf, len);
|
|
+ explicit_bzero(buf, len);
|
|
+ if (r != 0)
|
|
goto out;
|
|
}
|
|
out:
|
|
@@ -2375,9 +2393,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;
|
|
}
|
|
|
|
@@ -2496,6 +2517,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 ||
|
|
@@ -2509,7 +2532,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.
|
|
@@ -2518,10 +2541,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);
|
|
@@ -2529,12 +2552,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;
|
|
+ }
|
|
+
|
|
+ r = 0;
|
|
+out:
|
|
+ if (r != 0)
|
|
+ sshbuf_obfuscate(m);
|
|
debug3_f("done");
|
|
- return 0;
|
|
+ return r;
|
|
}
|
|
|
|
/* NEW API */
|
|
Index: openssh-9.3p2/packet.h
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/packet.h
|
|
+++ openssh-9.3p2/packet.h
|
|
@@ -103,6 +103,7 @@ void ssh_packet_close(struct ssh *);
|
|
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_clear_curkeys(struct ssh *, int);
|
|
void ssh_clear_newkeys(struct ssh *, int);
|
|
|
|
int ssh_packet_is_rekeying(struct ssh *);
|
|
Index: openssh-9.3p2/sshbuf.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/sshbuf.c
|
|
+++ openssh-9.3p2/sshbuf.c
|
|
@@ -309,6 +309,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-9.3p2/sshbuf.h
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/sshbuf.h
|
|
+++ openssh-9.3p2/sshbuf.h
|
|
@@ -298,6 +298,9 @@ int sshbuf_write_file(const char *path,
|
|
int sshbuf_read(int, struct sshbuf *, size_t, size_t *)
|
|
__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-9.3p2/sshd-session.c
|
|
===================================================================
|
|
--- openssh-9.3p2.orig/sshd-session.c
|
|
+++ openssh-9.3p2/sshd-session.c
|
|
@@ -197,6 +197,19 @@ static void do_ssh2_kex(struct ssh *);
|
|
static void do_ssh2_kex(struct ssh *);
|
|
|
|
/*
|
|
+ * 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);
|
|
+}
|
|
+
|
|
+/*
|
|
* Signal handler for the alarm after the login grace period has expired.
|
|
* As usual, this may only take signal-safe actions, even though it is
|
|
* terminal.
|
|
@@ -260,6 +260,8 @@ destroy_sensitive_data(struct ssh *ssh,
|
|
sensitive_data.host_certificates[i] = NULL;
|
|
}
|
|
}
|
|
+
|
|
+ clobber_stack();
|
|
}
|
|
|
|
/* Demote private to public keys for network child */
|
|
@@ -431,6 +432,8 @@ privsep_preauth(struct ssh *ssh)
|
|
{
|
|
int skip_privdrop = 0;
|
|
|
|
+ clobber_stack();
|
|
+
|
|
/*
|
|
* Hack for systems that don't support FD passing: retain privileges
|
|
* in the post-auth privsep process so it can allocate PTYs directly.
|
|
@@ -1354,6 +1356,7 @@ main(int ac, char **av)
|
|
*/
|
|
mm_send_keystate(ssh, pmonitor);
|
|
ssh_packet_clear_keys(ssh);
|
|
+ clobber_stack();
|
|
exit(0);
|
|
|
|
authenticated:
|
|
@@ -1431,6 +1434,7 @@ main(int ac, char **av)
|
|
|
|
mm_terminate();
|
|
|
|
+ clobber_stack();
|
|
exit(0);
|
|
}
|
|
|
|
@@ -1577,8 +1581,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;
|
|
extern int auth_attempted; /* monitor.c */
|
|
|
|
@@ -1604,5 +1610,7 @@ cleanup_exit(int i)
|
|
mm_is_monitor())
|
|
audit_event(the_active_state, SSH_CONNECTION_ABANDON);
|
|
#endif
|
|
+
|
|
+ clobber_stack();
|
|
_exit(i);
|
|
}
|