]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Enforce maximum packet/block limit during
authordjm@openbsd.org <djm@openbsd.org>
Tue, 30 Dec 2025 00:22:58 +0000 (00:22 +0000)
committerDamien Miller <djm@mindrot.org>
Tue, 30 Dec 2025 00:36:51 +0000 (11:36 +1100)
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

index 2a5a56a886da0005c869d413ecace0d1572eb547..2df7a97b7e23e2288f1cf106ae2429e86a12a9ca 100644 (file)
--- 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@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@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)
+/*
+ * 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;
+
+       /*
+        * 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;
-       u_int32_t out_blocks;
 
-       /* XXX client can't cope with rekeying pre-auth */
+       /* 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.