[openssh-commits] [openssh] 02/04: upstream: Enforce maximum packet/block limit during

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Dec 30 11:37:52 AEDT 2025


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit ca313fef2deed90668fe0706da8529310092d1dd
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Tue Dec 30 00:22:58 2025 +0000

    upstream: Enforce maximum packet/block limit during
    
    pre-authentication phase
    
    OpenSSH doesn't support rekeying before authentication completes to
    minimise pre-auth attack surface.
    
    Given LoginGraceTime, MaxAuthTries and strict KEX, it would be
    difficult to send enough data or packets before authentication
    completes to reach a point where rekeying is required, but we'd
    prefer it to be completely impossible.
    
    So this applies the default volume/packet rekeying limits to the
    pre-auth phase. If these limits are exceeded the connection will
    simply be closed.
    
    ok dtucker markus
    
    OpenBSD-Commit-ID: 70415098db739058006e4ebd1630b6bae8cc8bf6
---
 packet.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 68 insertions(+), 22 deletions(-)

diff --git a/packet.c b/packet.c
index 2a5a56a88..2df7a97b7 100644
--- a/packet.c
+++ b/packet.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.c,v 1.327 2025/12/05 06:16:27 dtucker Exp $ */
+/* $OpenBSD: packet.c,v 1.328 2025/12/30 00:22:58 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -184,6 +184,7 @@ struct session_state {
 	struct packet_state p_read, p_send;
 
 	/* Volume-based rekeying */
+	u_int64_t hard_max_blocks_in, hard_max_blocks_out;
 	u_int64_t max_blocks_in, max_blocks_out, rekey_limit;
 
 	/* Time-based rekeying */
@@ -979,7 +980,7 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
 	struct sshcomp *comp;
 	struct sshcipher_ctx **ccp;
 	struct packet_state *ps;
-	u_int64_t *max_blocks;
+	u_int64_t *max_blocks, *hard_max_blocks;
 	const char *wmsg;
 	int r, crypt_type;
 	const char *dir = mode == MODE_OUT ? "out" : "in";
@@ -990,11 +991,13 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
 		ccp = &state->send_context;
 		crypt_type = CIPHER_ENCRYPT;
 		ps = &state->p_send;
+		hard_max_blocks = &state->hard_max_blocks_out;
 		max_blocks = &state->max_blocks_out;
 	} else {
 		ccp = &state->receive_context;
 		crypt_type = CIPHER_DECRYPT;
 		ps = &state->p_read;
+		hard_max_blocks = &state->hard_max_blocks_in;
 		max_blocks = &state->max_blocks_in;
 	}
 	if (state->newkeys[mode] != NULL) {
@@ -1055,25 +1058,59 @@ ssh_set_newkeys(struct ssh *ssh, int mode)
 	 * See RFC4344 section 3.2.
 	 */
 	if (enc->block_size >= 16)
-		*max_blocks = (u_int64_t)1 << (enc->block_size*2);
+		*hard_max_blocks = (u_int64_t)1 << (enc->block_size*2);
 	else
-		*max_blocks = ((u_int64_t)1 << 30) / enc->block_size;
-	if (state->rekey_limit)
+		*hard_max_blocks = ((u_int64_t)1 << 30) / enc->block_size;
+	*max_blocks = *hard_max_blocks;
+	if (state->rekey_limit) {
 		*max_blocks = MINIMUM(*max_blocks,
 		    state->rekey_limit / enc->block_size);
+	}
 	debug("rekey %s after %llu blocks", dir,
 	    (unsigned long long)*max_blocks);
 	return 0;
 }
 
 #define MAX_PACKETS	(1U<<31)
-static int
-ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len)
+/*
+ * Checks whether the packet- or block- based rekeying limits have been
+ * exceeded. If the 'hard' flag is set, the checks are performed against the
+ * absolute maximum we're willing to accept for the given cipher. Otherwise
+ * the checks are performed against the RekeyLimit volume, which may be lower.
+ */
+static inline int
+ssh_packet_check_rekey_blocklimit(struct ssh *ssh, u_int packet_len, int hard)
 {
 	struct session_state *state = ssh->state;
 	u_int32_t out_blocks;
+	const u_int64_t max_blocks_in = hard ?
+	    state->hard_max_blocks_in : state->max_blocks_in;
+	const u_int64_t max_blocks_out = hard ?
+	    state->hard_max_blocks_out : state->max_blocks_out;
 
-	/* XXX client can't cope with rekeying pre-auth */
+	/*
+	 * Always rekey when MAX_PACKETS sent in either direction
+	 * As per RFC4344 section 3.1 we do this after 2^31 packets.
+	 */
+	if (state->p_send.packets > MAX_PACKETS ||
+	    state->p_read.packets > MAX_PACKETS)
+		return 1;
+
+	/* Rekey after (cipher-specific) maximum blocks */
+	out_blocks = ROUNDUP(packet_len,
+	    state->newkeys[MODE_OUT]->enc.block_size);
+	return (max_blocks_out &&
+	    (state->p_send.blocks + out_blocks > max_blocks_out)) ||
+	    (max_blocks_in &&
+	    (state->p_read.blocks > max_blocks_in));
+}
+
+static int
+ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len)
+{
+	struct session_state *state = ssh->state;
+
+	/* Don't attempt rekeying during pre-auth */
 	if (!state->after_authentication)
 		return 0;
 
@@ -1097,26 +1134,30 @@ ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len)
 	    (int64_t)state->rekey_time + state->rekey_interval <= monotime())
 		return 1;
 
-	/*
-	 * Always rekey when MAX_PACKETS sent in either direction
-	 * As per RFC4344 section 3.1 we do this after 2^31 packets.
-	 */
-	if (state->p_send.packets > MAX_PACKETS ||
-	    state->p_read.packets > MAX_PACKETS)
-		return 1;
+	return ssh_packet_check_rekey_blocklimit(ssh, outbound_packet_len, 0);
+}
 
-	/* Rekey after (cipher-specific) maximum blocks */
-	out_blocks = ROUNDUP(outbound_packet_len,
-	    state->newkeys[MODE_OUT]->enc.block_size);
-	return (state->max_blocks_out &&
-	    (state->p_send.blocks + out_blocks > state->max_blocks_out)) ||
-	    (state->max_blocks_in &&
-	    (state->p_read.blocks > state->max_blocks_in));
+/* Checks that the hard rekey limits have not been exceeded during preauth */
+static int
+ssh_packet_check_rekey_preauth(struct ssh *ssh, u_int outgoing_packet_len)
+{
+	if (ssh->state->after_authentication)
+		return 0;
+
+	if (ssh_packet_check_rekey_blocklimit(ssh, 0, 1)) {
+		error("RekeyLimit exceeded before authentication completed");
+		return SSH_ERR_NEED_REKEY;
+	}
+	return 0;
 }
 
 int
 ssh_packet_check_rekey(struct ssh *ssh)
 {
+	int r;
+
+	if ((r = ssh_packet_check_rekey_preauth(ssh, 0)) != 0)
+		return r;
 	if (!ssh_packet_need_rekeying(ssh, 0))
 		return 0;
 	debug3_f("rekex triggered");
@@ -1374,6 +1415,11 @@ ssh_packet_send2(struct ssh *ssh)
 	need_rekey = !ssh_packet_type_is_kex(type) &&
 	    ssh_packet_need_rekeying(ssh, sshbuf_len(state->outgoing_packet));
 
+	/* Enforce hard rekey limit during pre-auth */
+	if (!state->rekeying && !ssh_packet_type_is_kex(type) &&
+	    (r = ssh_packet_check_rekey_preauth(ssh, 0)) != 0)
+		return r;
+
 	/*
 	 * During rekeying we can only send key exchange messages.
 	 * Queue everything else.

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list