aboutsummaryrefslogtreecommitdiff
path: root/crypto/openssh/packet.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssh/packet.c')
-rw-r--r--crypto/openssh/packet.c304
1 files changed, 253 insertions, 51 deletions
diff --git a/crypto/openssh/packet.c b/crypto/openssh/packet.c
index 5dd8269c218a..190a579d1e50 100644
--- a/crypto/openssh/packet.c
+++ b/crypto/openssh/packet.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: packet.c,v 1.323 2025/09/25 06:33:19 djm Exp $ */
+/* $OpenBSD: packet.c,v 1.334 2026/03/03 09:57:25 dtucker Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -40,12 +40,11 @@
#include "includes.h"
#include <sys/types.h>
-#include "openbsd-compat/sys-queue.h"
+#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
-#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
@@ -59,6 +58,7 @@
#include <poll.h>
#include <signal.h>
#include <time.h>
+#include <util.h>
/*
* Explicitly include OpenSSL before zlib as some versions of OpenSSL have
@@ -80,15 +80,12 @@
#include "compat.h"
#include "ssh2.h"
#include "cipher.h"
-#include "sshkey.h"
#include "kex.h"
#include "digest.h"
#include "mac.h"
#include "log.h"
#include "canohost.h"
#include "misc.h"
-#include "channels.h"
-#include "ssh.h"
#include "packet.h"
#include "ssherr.h"
#include "sshbuf.h"
@@ -102,10 +99,10 @@
#define PACKET_MAX_SIZE (256 * 1024)
struct packet_state {
- u_int32_t seqnr;
- u_int32_t packets;
- u_int64_t blocks;
- u_int64_t bytes;
+ uint32_t seqnr;
+ uint32_t packets;
+ uint64_t blocks;
+ uint64_t bytes;
};
struct packet {
@@ -183,10 +180,11 @@ struct session_state {
struct packet_state p_read, p_send;
/* Volume-based rekeying */
- u_int64_t max_blocks_in, max_blocks_out, rekey_limit;
+ uint64_t hard_max_blocks_in, hard_max_blocks_out;
+ uint64_t max_blocks_in, max_blocks_out, rekey_limit;
/* Time-based rekeying */
- u_int32_t rekey_interval; /* how often in seconds */
+ uint32_t rekey_interval; /* how often in seconds */
time_t rekey_time; /* time of last rekeying */
/* roundup current message to extra_pad bytes */
@@ -471,7 +469,7 @@ ssh_packet_connection_is_on_socket(struct ssh *ssh)
}
void
-ssh_packet_get_bytes(struct ssh *ssh, u_int64_t *ibytes, u_int64_t *obytes)
+ssh_packet_get_bytes(struct ssh *ssh, uint64_t *ibytes, uint64_t *obytes)
{
if (ibytes)
*ibytes = ssh->state->p_read.bytes;
@@ -978,7 +976,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;
+ uint64_t *max_blocks, *hard_max_blocks;
const char *wmsg;
int r, crypt_type;
const char *dir = mode == MODE_OUT ? "out" : "in";
@@ -989,11 +987,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) {
@@ -1054,25 +1054,62 @@ 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 = (uint64_t)1 << (enc->block_size*2);
else
- *max_blocks = ((u_int64_t)1 << 30) / enc->block_size;
- if (state->rekey_limit)
+ *hard_max_blocks = ((uint64_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;
+ uint32_t out_blocks;
+ const uint64_t max_blocks_in = hard ?
+ state->hard_max_blocks_in : state->max_blocks_in;
+ const uint64_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;
+
+ if (state->newkeys[MODE_OUT] == NULL)
+ return 0;
+
+ /* 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;
@@ -1080,10 +1117,6 @@ ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len)
if (ssh_packet_is_rekeying(ssh))
return 0;
- /* Peer can't rekey */
- if (ssh->compat & SSH_BUG_NOREKEY)
- return 0;
-
/*
* Permit one packet in or out per rekey - this allows us to
* make progress when rekey limits are very small.
@@ -1096,26 +1129,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");
@@ -1326,8 +1363,7 @@ ssh_packet_send2_wrapped(struct ssh *ssh)
logit("outgoing seqnr wraps around");
}
if (++state->p_send.packets == 0)
- if (!(ssh->compat & SSH_BUG_NOREKEY))
- return SSH_ERR_NEED_REKEY;
+ return SSH_ERR_NEED_REKEY;
state->p_send.blocks += len / block_size;
state->p_send.bytes += len;
sshbuf_reset(state->outgoing_packet);
@@ -1373,6 +1409,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.
@@ -1444,7 +1485,7 @@ ssh_packet_send2(struct ssh *ssh)
*/
int
-ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, uint32_t *seqnr_p)
{
struct session_state *state = ssh->state;
int len, r, ms_remain = 0;
@@ -1537,7 +1578,7 @@ ssh_packet_read(struct ssh *ssh)
}
static int
-ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, uint32_t *seqnr_p)
{
struct session_state *state = ssh->state;
const u_char *cp;
@@ -1575,7 +1616,7 @@ ssh_packet_read_poll2_mux(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
}
int
-ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, uint32_t *seqnr_p)
{
struct session_state *state = ssh->state;
u_int padlen, need;
@@ -1737,8 +1778,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
logit("incoming seqnr wraps around");
}
if (++state->p_read.packets == 0)
- if (!(ssh->compat & SSH_BUG_NOREKEY))
- return SSH_ERR_NEED_REKEY;
+ return SSH_ERR_NEED_REKEY;
state->p_read.blocks += (state->packlen + 4) / block_size;
state->p_read.bytes += state->packlen + 4;
@@ -1813,7 +1853,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
}
int
-ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p)
+ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, uint32_t *seqnr_p)
{
struct session_state *state = ssh->state;
u_int reason, seqnr;
@@ -2302,7 +2342,7 @@ ssh_packet_get_maxsize(struct ssh *ssh)
}
void
-ssh_packet_set_rekey_limits(struct ssh *ssh, u_int64_t bytes, u_int32_t seconds)
+ssh_packet_set_rekey_limits(struct ssh *ssh, uint64_t bytes, uint32_t seconds)
{
debug3("rekey after %llu bytes, %u seconds", (unsigned long long)bytes,
(unsigned int)seconds);
@@ -2650,13 +2690,13 @@ sshpkt_put_u8(struct ssh *ssh, u_char val)
}
int
-sshpkt_put_u32(struct ssh *ssh, u_int32_t val)
+sshpkt_put_u32(struct ssh *ssh, uint32_t val)
{
return sshbuf_put_u32(ssh->state->outgoing_packet, val);
}
int
-sshpkt_put_u64(struct ssh *ssh, u_int64_t val)
+sshpkt_put_u64(struct ssh *ssh, uint64_t val)
{
return sshbuf_put_u64(ssh->state->outgoing_packet, val);
}
@@ -2716,13 +2756,13 @@ sshpkt_get_u8(struct ssh *ssh, u_char *valp)
}
int
-sshpkt_get_u32(struct ssh *ssh, u_int32_t *valp)
+sshpkt_get_u32(struct ssh *ssh, uint32_t *valp)
{
return sshbuf_get_u32(ssh->state->incoming_packet, valp);
}
int
-sshpkt_get_u64(struct ssh *ssh, u_int64_t *valp)
+sshpkt_get_u64(struct ssh *ssh, uint64_t *valp)
{
return sshbuf_get_u64(ssh->state->incoming_packet, valp);
}
@@ -2847,7 +2887,7 @@ ssh_packet_send_mux(struct ssh *ssh)
int
sshpkt_msg_ignore(struct ssh *ssh, u_int nbytes)
{
- u_int32_t rnd = 0;
+ uint32_t rnd = 0;
int r;
u_int i;
@@ -2902,3 +2942,165 @@ sshpkt_add_padding(struct ssh *ssh, u_char pad)
ssh->state->extra_pad = pad;
return 0;
}
+
+static char *
+format_traffic_stats(struct packet_state *ps)
+{
+ char *stats = NULL, bytes[FMT_SCALED_STRSIZE];
+
+ if (ps->bytes > LLONG_MAX || fmt_scaled(ps->bytes, bytes) != 0)
+ strlcpy(bytes, "OVERFLOW", sizeof(bytes));
+
+ xasprintf(&stats, "%lu pkts %llu blks %sB",
+ (unsigned long)ps->packets, (unsigned long long)ps->blocks, bytes);
+ return stats;
+}
+
+static char *
+dedupe_alg_names(const char *in, const char *out)
+{
+ char *names = NULL;
+
+ if (in == NULL)
+ in = "<implicit>";
+ if (out == NULL)
+ out = "<implicit>";
+
+ if (strcmp(in, out) == 0) {
+ names = xstrdup(in);
+ } else {
+ xasprintf(&names, "%s in, %s out", in, out);
+ }
+ return names;
+}
+
+static char *
+comp_status_message(struct ssh *ssh)
+{
+#ifdef WITH_ZLIB
+ char *ret = NULL;
+ struct session_state *state = ssh->state;
+ unsigned long long iraw = 0, icmp = 0, oraw = 0, ocmp = 0;
+ char iraw_f[FMT_SCALED_STRSIZE] = "", oraw_f[FMT_SCALED_STRSIZE] = "";
+ char icmp_f[FMT_SCALED_STRSIZE] = "", ocmp_f[FMT_SCALED_STRSIZE] = "";
+
+ if (state->compression_buffer) {
+ if (state->compression_in_started) {
+ iraw = state->compression_in_stream.total_out;
+ icmp = state->compression_in_stream.total_in;
+ if (fmt_scaled(iraw, iraw_f) != 0)
+ strlcpy(iraw_f, "OVERFLOW", sizeof(iraw_f));
+ if (fmt_scaled(icmp, icmp_f) != 0)
+ strlcpy(icmp_f, "OVERFLOW", sizeof(icmp_f));
+ }
+ if (state->compression_out_started) {
+ oraw = state->compression_out_stream.total_in;
+ ocmp = state->compression_out_stream.total_out;
+ if (fmt_scaled(oraw, oraw_f) != 0)
+ strlcpy(oraw_f, "OVERFLOW", sizeof(oraw_f));
+ if (fmt_scaled(ocmp, ocmp_f) != 0)
+ strlcpy(ocmp_f, "OVERFLOW", sizeof(ocmp_f));
+ }
+ xasprintf(&ret,
+ " compressed %s/%s (*%.3f) in,"
+ " %s/%s (*%.3f) out\r\n",
+ icmp_f, iraw_f, iraw == 0 ? 0.0 : (double)icmp / iraw,
+ ocmp_f, oraw_f, oraw == 0 ? 0.0 : (double)ocmp / oraw);
+ return ret;
+ }
+#endif /* WITH_ZLIB */
+ return xstrdup("");
+}
+
+char *
+connection_info_message(struct ssh *ssh)
+{
+ char *ret = NULL, *cipher = NULL, *mac = NULL, *comp = NULL;
+ char *rekey_volume = NULL, *rekey_time = NULL, *comp_info = NULL;
+ char thishost[NI_MAXHOST] = "unknown", *tcp_info = NULL;
+ struct kex *kex;
+ struct session_state *state;
+ struct newkeys *nk_in, *nk_out;
+ char *stats_in = NULL, *stats_out = NULL;
+ uint64_t epoch = (uint64_t)time(NULL) - monotime();
+
+ if (ssh == NULL)
+ return NULL;
+ state = ssh->state;
+ kex = ssh->kex;
+
+ (void)gethostname(thishost, sizeof(thishost));
+
+ if (ssh_local_port(ssh) != 65535 ||
+ strcmp(ssh_local_ipaddr(ssh), "UNKNOWN") != 0) {
+ xasprintf(&tcp_info, " tcp %s:%d -> %s:%d\r\n",
+ ssh_local_ipaddr(ssh), ssh_local_port(ssh),
+ ssh_remote_ipaddr(ssh), ssh_remote_port(ssh));
+ } else {
+ tcp_info = xstrdup("");
+ }
+
+ nk_in = ssh->state->newkeys[MODE_IN];
+ nk_out = ssh->state->newkeys[MODE_OUT];
+ stats_in = format_traffic_stats(&ssh->state->p_read);
+ stats_out = format_traffic_stats(&ssh->state->p_send);
+
+ cipher = dedupe_alg_names(nk_in->enc.name, nk_out->enc.name);
+ mac = dedupe_alg_names(nk_in->mac.name, nk_out->mac.name);
+ comp = dedupe_alg_names(nk_in->comp.name, nk_out->comp.name);
+
+ /* Volume based rekeying. */
+ if (state->rekey_limit == 0) {
+ xasprintf(&rekey_volume, "limit none");
+ } else {
+ char *volumes = NULL, in[32], out[32];
+
+ snprintf(in, sizeof(in), "%llu",
+ (unsigned long long)state->max_blocks_in);
+ snprintf(out, sizeof(out), "%llu",
+ (unsigned long long)state->max_blocks_out);
+ volumes = dedupe_alg_names(in, out);
+ xasprintf(&rekey_volume, "limit blocks %s", volumes);
+ free(volumes);
+ }
+
+ /* Time based rekeying. */
+ if (state->rekey_interval == 0) {
+ rekey_time = xstrdup("interval none");
+ } else {
+ char rekey_next[64];
+
+ format_absolute_time(epoch + state->rekey_time +
+ state->rekey_interval, rekey_next, sizeof(rekey_next));
+ xasprintf(&rekey_time, "interval %s, next %s",
+ fmt_timeframe(state->rekey_interval), rekey_next);
+ }
+ comp_info = comp_status_message(ssh);
+
+ xasprintf(&ret, "Connection information for %s pid %lld\r\n"
+ "%s"
+ " kexalgorithm %s\r\n hostkeyalgorithm %s\r\n"
+ " cipher %s\r\n mac %s\r\n compression %s\r\n"
+ " rekey %s %s\r\n"
+ " traffic %s in, %s out\r\n"
+ "%s",
+ thishost, (long long)getpid(),
+ tcp_info,
+ kex->name, kex->hostkey_alg,
+ cipher, mac, comp,
+ rekey_volume, rekey_time,
+ stats_in, stats_out,
+ comp_info
+ );
+ free(tcp_info);
+ free(cipher);
+ free(mac);
+ free(comp);
+ free(stats_in);
+ free(stats_out);
+ free(rekey_volume);
+ free(rekey_time);
+ free(comp_info);
+ return ret;
+}
+