From 12cd8aa4a28ed5e45b3d67767dce4baae1414b4f Mon Sep 17 00:00:00 2001 From: purplerain Date: Mon, 28 Aug 2023 17:23:15 +0000 Subject: [PATCH] sync code with last improvements from OpenBSD --- sys/dev/dt/dt_prov_static.c | 4 +- sys/netinet/tcp_input.c | 49 +++++++------ sys/netinet/tcp_var.h | 3 +- sys/sys/refcnt.h | 5 +- usr.bin/ssh/PROTOCOL | 35 +++++++++- usr.bin/ssh/auth2.c | 11 ++- usr.bin/ssh/clientloop.c | 133 ++++++++++++++++++++++++++++++++++-- usr.bin/ssh/kex.c | 47 +++++++++---- usr.bin/ssh/kex.h | 3 +- usr.bin/ssh/misc.c | 31 ++++++--- usr.bin/ssh/misc.h | 3 +- usr.bin/ssh/packet.c | 35 +++++++++- usr.bin/ssh/packet.h | 3 +- usr.bin/ssh/readconf.c | 64 ++++++++++++++++- usr.bin/ssh/readconf.h | 8 ++- usr.bin/ssh/ssh2.h | 6 +- usr.bin/ssh/ssh_config.5 | 23 ++++++- 17 files changed, 397 insertions(+), 66 deletions(-) diff --git a/sys/dev/dt/dt_prov_static.c b/sys/dev/dt/dt_prov_static.c index 0d6783280..d03badccb 100644 --- a/sys/dev/dt/dt_prov_static.c +++ b/sys/dev/dt/dt_prov_static.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dt_prov_static.c,v 1.21 2023/08/14 08:33:24 mpi Exp $ */ +/* $OpenBSD: dt_prov_static.c,v 1.22 2023/08/28 14:50:01 bluhm Exp $ */ /* * Copyright (c) 2019 Martin Pieuchot @@ -100,6 +100,7 @@ DT_STATIC_PROBE3(refcnt, ifaddr, "void *", "int", "int"); DT_STATIC_PROBE3(refcnt, ifmaddr, "void *", "int", "int"); DT_STATIC_PROBE3(refcnt, inpcb, "void *", "int", "int"); DT_STATIC_PROBE3(refcnt, rtentry, "void *", "int", "int"); +DT_STATIC_PROBE3(refcnt, syncache, "void *", "int", "int"); DT_STATIC_PROBE3(refcnt, tdb, "void *", "int", "int"); /* @@ -152,6 +153,7 @@ struct dt_probe *const dtps_static[] = { &_DT_STATIC_P(refcnt, ifmaddr), &_DT_STATIC_P(refcnt, inpcb), &_DT_STATIC_P(refcnt, rtentry), + &_DT_STATIC_P(refcnt, syncache), &_DT_STATIC_P(refcnt, tdb), }; diff --git a/sys/netinet/tcp_input.c b/sys/netinet/tcp_input.c index 658597806..06a3bf7a6 100644 --- a/sys/netinet/tcp_input.c +++ b/sys/netinet/tcp_input.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tcp_input.c,v 1.389 2023/07/06 09:15:23 bluhm Exp $ */ +/* $OpenBSD: tcp_input.c,v 1.390 2023/08/28 14:50:01 bluhm Exp $ */ /* $NetBSD: tcp_input.c,v 1.23 1996/02/13 23:43:44 christos Exp $ */ /* @@ -192,7 +192,6 @@ void syn_cache_put(struct syn_cache *); void syn_cache_rm(struct syn_cache *); int syn_cache_respond(struct syn_cache *, struct mbuf *, uint64_t); void syn_cache_timer(void *); -void syn_cache_reaper(void *); void syn_cache_insert(struct syn_cache *, struct tcpcb *); void syn_cache_reset(struct sockaddr *, struct sockaddr *, struct tcphdr *, u_int); @@ -3091,6 +3090,7 @@ int tcp_syn_cache_limit = TCP_SYN_HASH_SIZE*TCP_SYN_BUCKET_SIZE; int tcp_syn_bucket_limit = 3*TCP_SYN_BUCKET_SIZE; int tcp_syn_use_limit = 100000; +struct pool syn_cache_pool; struct syn_cache_set tcp_syn_cache[2]; int tcp_syn_cache_active; @@ -3138,25 +3138,27 @@ syn_cache_rm(struct syn_cache *sc) TAILQ_REMOVE(&sc->sc_buckethead->sch_bucket, sc, sc_bucketq); sc->sc_tp = NULL; LIST_REMOVE(sc, sc_tpq); + refcnt_rele(&sc->sc_refcnt); sc->sc_buckethead->sch_length--; - timeout_del(&sc->sc_timer); + if (timeout_del(&sc->sc_timer)) + refcnt_rele(&sc->sc_refcnt); sc->sc_set->scs_count--; } void syn_cache_put(struct syn_cache *sc) { + if (refcnt_rele(&sc->sc_refcnt) == 0) + return; + m_free(sc->sc_ipopts); if (sc->sc_route4.ro_rt != NULL) { rtfree(sc->sc_route4.ro_rt); sc->sc_route4.ro_rt = NULL; } - timeout_set(&sc->sc_timer, syn_cache_reaper, sc); - timeout_add(&sc->sc_timer, 0); + pool_put(&syn_cache_pool, sc); } -struct pool syn_cache_pool; - /* * We don't estimate RTT with SYNs, so each packet starts with the default * RTT and each timer step has a fixed timeout value. @@ -3166,9 +3168,8 @@ do { \ TCPT_RANGESET((sc)->sc_rxtcur, \ TCPTV_SRTTDFLT * tcp_backoff[(sc)->sc_rxtshift], TCPTV_MIN, \ TCPTV_REXMTMAX); \ - if (!timeout_initialized(&(sc)->sc_timer)) \ - timeout_set_proc(&(sc)->sc_timer, syn_cache_timer, (sc)); \ - timeout_add_msec(&(sc)->sc_timer, (sc)->sc_rxtcur); \ + if (timeout_add_msec(&(sc)->sc_timer, (sc)->sc_rxtcur)) \ + refcnt_take(&(sc)->sc_refcnt); \ } while (/*CONSTCOND*/0) void @@ -3306,6 +3307,7 @@ syn_cache_insert(struct syn_cache *sc, struct tcpcb *tp) SYN_CACHE_TIMER_ARM(sc); /* Link it from tcpcb entry */ + refcnt_take(&sc->sc_refcnt); LIST_INSERT_HEAD(&tp->t_sc, sc, sc_tpq); /* Put it into the bucket. */ @@ -3336,10 +3338,11 @@ syn_cache_timer(void *arg) { struct syn_cache *sc = arg; uint64_t now; + int lastref; NET_LOCK(); if (sc->sc_flags & SCF_DEAD) - goto out; + goto freeit; now = tcp_now(); @@ -3364,26 +3367,28 @@ syn_cache_timer(void *arg) sc->sc_rxtshift++; SYN_CACHE_TIMER_ARM(sc); - out: + /* + * Decrement reference of this timer. We know there is another timer + * as we just added it. So just deref, free is not necessary. + */ + lastref = refcnt_rele(&sc->sc_refcnt); + KASSERT(lastref == 0); + (void)lastref; NET_UNLOCK(); return; dropit: tcpstat_inc(tcps_sc_timed_out); syn_cache_rm(sc); + /* Decrement reference of the timer and free object after remove. */ + lastref = refcnt_rele(&sc->sc_refcnt); + KASSERT(lastref == 0); + (void)lastref; + freeit: syn_cache_put(sc); NET_UNLOCK(); } -void -syn_cache_reaper(void *arg) -{ - struct syn_cache *sc = arg; - - pool_put(&syn_cache_pool, (sc)); - return; -} - /* * Remove syn cache created by the specified tcb entry, * because this does not make sense to keep them @@ -3826,6 +3831,8 @@ syn_cache_add(struct sockaddr *src, struct sockaddr *dst, struct tcphdr *th, m_free(ipopts); return (-1); } + refcnt_init_trace(&sc->sc_refcnt, DT_REFCNT_IDX_SYNCACHE); + timeout_set_proc(&sc->sc_timer, syn_cache_timer, sc); /* * Fill in the cache, and put the necessary IP and TCP diff --git a/sys/netinet/tcp_var.h b/sys/netinet/tcp_var.h index 284f2b625..be7e986f7 100644 --- a/sys/netinet/tcp_var.h +++ b/sys/netinet/tcp_var.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tcp_var.h,v 1.169 2023/07/06 09:15:24 bluhm Exp $ */ +/* $OpenBSD: tcp_var.h,v 1.170 2023/08/28 14:50:02 bluhm Exp $ */ /* $NetBSD: tcp_var.h,v 1.17 1996/02/13 23:44:24 christos Exp $ */ /* @@ -236,6 +236,7 @@ union syn_cache_sa { struct syn_cache { TAILQ_ENTRY(syn_cache) sc_bucketq; /* link on bucket list */ + struct refcnt sc_refcnt; /* ref count list and timer */ struct timeout sc_timer; /* rexmt timer */ union { /* cached route */ struct route route4; diff --git a/sys/sys/refcnt.h b/sys/sys/refcnt.h index c0f048214..38fb995e1 100644 --- a/sys/sys/refcnt.h +++ b/sys/sys/refcnt.h @@ -1,4 +1,4 @@ -/* $OpenBSD: refcnt.h,v 1.11 2023/07/06 19:46:53 kn Exp $ */ +/* $OpenBSD: refcnt.h,v 1.12 2023/08/28 14:50:02 bluhm Exp $ */ /* * Copyright (c) 2015 David Gwynne @@ -49,7 +49,8 @@ unsigned int refcnt_read(struct refcnt *); #define DT_REFCNT_IDX_IFMADDR 3 #define DT_REFCNT_IDX_INPCB 4 #define DT_REFCNT_IDX_RTENTRY 5 -#define DT_REFCNT_IDX_TDB 6 +#define DT_REFCNT_IDX_SYNCACHE 6 +#define DT_REFCNT_IDX_TDB 7 #endif /* _KERNEL */ diff --git a/usr.bin/ssh/PROTOCOL b/usr.bin/ssh/PROTOCOL index 0aeefbb1a..9e15c11ab 100644 --- a/usr.bin/ssh/PROTOCOL +++ b/usr.bin/ssh/PROTOCOL @@ -104,6 +104,39 @@ http://git.libssh.org/users/aris/libssh.git/plain/doc/curve25519-sha256@libssh.o This is identical to curve25519-sha256 as later published in RFC8731. +1.9 transport: ping facility + +OpenSSH implements a transport level ping message SSH2_MSG_PING +and a corresponding SSH2_MSG_PONG reply. + +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + +The ping message is simply: + + byte SSH_MSG_PING + string data + +The reply copies the data (which may be the empty string) from the +ping: + + byte SSH_MSG_PONG + string data + +Replies are sent in order. They are sent immediately except when rekeying +is in progress, in which case they are queued until rekeying completes. + +The server advertises support for these messages using the +SSH2_MSG_EXT_INFO mechanism (RFC8308), with the following message: + + string "ping@openssh.com" + string "0" (version) + +The ping/reply message is implemented at the transport layer rather +than as a named global or channel request to allow pings with very +short packet lengths, which would not be possible with other +approaches. + 2. Connection protocol changes 2.1. connection: Channel write close extension "eow@openssh.com" @@ -712,4 +745,4 @@ master instance and later clients. OpenSSH extends the usual agent protocol. These changes are documented in the PROTOCOL.agent file. -$OpenBSD: PROTOCOL,v 1.48 2022/11/07 01:53:01 dtucker Exp $ +$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $ diff --git a/usr.bin/ssh/auth2.c b/usr.bin/ssh/auth2.c index 1cebd3f2d..2d5dbcb5b 100644 --- a/usr.bin/ssh/auth2.c +++ b/usr.bin/ssh/auth2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2.c,v 1.166 2023/03/08 04:43:12 guenther Exp $ */ +/* $OpenBSD: auth2.c,v 1.167 2023/08/28 09:48:11 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -208,6 +208,7 @@ input_service_request(int type, u_int32_t seq, struct ssh *ssh) } #define MIN_FAIL_DELAY_SECONDS 0.005 +#define MAX_FAIL_DELAY_SECONDS 5.0 static double user_specific_delay(const char *user) { @@ -233,6 +234,12 @@ ensure_minimum_time_since(double start, double seconds) struct timespec ts; double elapsed = monotime_double() - start, req = seconds, remain; + if (elapsed > MAX_FAIL_DELAY_SECONDS) { + debug3_f("elapsed %0.3lfms exceeded the max delay " + "requested %0.3lfms)", elapsed*1000, req*1000); + return; + } + /* if we've already passed the requested time, scale up */ while ((remain = seconds - elapsed) < 0.0) seconds *= 2; @@ -317,7 +324,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh) debug2("input_userauth_request: try method %s", method); authenticated = m->userauth(ssh, method); } - if (!authctxt->authenticated) + if (!authctxt->authenticated && strcmp(method, "none") != 0) ensure_minimum_time_since(tstart, user_specific_delay(authctxt->user)); userauth_finish(ssh, authenticated, method, NULL); diff --git a/usr.bin/ssh/clientloop.c b/usr.bin/ssh/clientloop.c index 67a9f1736..035643f9e 100644 --- a/usr.bin/ssh/clientloop.c +++ b/usr.bin/ssh/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.392 2023/04/03 08:10:54 dtucker Exp $ */ +/* $OpenBSD: clientloop.c,v 1.394 2023/08/28 04:06:52 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -498,6 +498,128 @@ server_alive_check(struct ssh *ssh) schedule_server_alive_check(); } +/* Try to send a dummy keystroke */ +static int +send_chaff(struct ssh *ssh) +{ + int r; + + if ((ssh->kex->flags & KEX_HAS_PING) == 0) + return 0; + /* XXX probabilistically send chaff? */ + /* + * a SSH2_MSG_CHANNEL_DATA payload is 9 bytes: + * 4 bytes channel ID + 4 bytes string length + 1 byte string data + * simulate that here. + */ + if ((r = sshpkt_start(ssh, SSH2_MSG_PING)) != 0 || + (r = sshpkt_put_cstring(ssh, "PING!")) != 0 || + (r = sshpkt_send(ssh)) != 0) + fatal_fr(r, "send packet"); + return 1; +} + +/* + * Performs keystroke timing obfuscation. Returns non-zero if the + * output fd should be polled. + */ +static int +obfuscate_keystroke_timing(struct ssh *ssh, struct timespec *timeout) +{ + static int active; + static struct timespec next_interval, chaff_until; + struct timespec now, tmp; + int just_started = 0, had_keystroke = 0; + static unsigned long long nchaff; + char *stop_reason = NULL; + long long n; + + monotime_ts(&now); + + if (options.obscure_keystroke_timing_interval <= 0) + return 1; /* disabled in config */ + + if (!channel_still_open(ssh) || quit_pending) { + /* Stop if no channels left of we're waiting for one to close */ + stop_reason = "no active channels"; + } else if (ssh_packet_is_rekeying(ssh)) { + /* Stop if we're rekeying */ + stop_reason = "rekeying started"; + } else if (!ssh_packet_interactive_data_to_write(ssh) && + ssh_packet_have_data_to_write(ssh)) { + /* Stop if the output buffer has more than a few keystrokes */ + stop_reason = "output buffer filling"; + } else if (active && ssh_packet_have_data_to_write(ssh)) { + /* Still in active mode and have a keystroke queued. */ + had_keystroke = 1; + } else if (active) { + if (timespeccmp(&now, &chaff_until, >=)) { + /* Stop if there have been no keystrokes for a while */ + stop_reason = "chaff time expired"; + } else if (timespeccmp(&now, &next_interval, >=)) { + /* Otherwise if we were due to send, then send chaff */ + if (send_chaff(ssh)) + nchaff++; + } + } + + if (stop_reason != NULL) { + active = 0; + debug3_f("stopping: %s (%llu chaff packets sent)", + stop_reason, nchaff); + return 1; + } + + /* + * If we're in interactive mode, and only have a small amount + * of outbound data, then we assume that the user is typing + * interactively. In this case, start quantising outbound packets to + * fixed time intervals to hide inter-keystroke timing. + */ + if (!active && ssh_packet_interactive_data_to_write(ssh)) { + debug3_f("starting: interval %d", + options.obscure_keystroke_timing_interval); + just_started = had_keystroke = active = 1; + nchaff = 0; + ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval); + timespecadd(&now, &tmp, &next_interval); + } + + /* Don't hold off if obfuscation inactive */ + if (!active) + return 1; + + if (had_keystroke) { + /* + * Arrange to send chaff packets for a random interval after + * the last keystroke was sent. + */ + ms_to_timespec(&tmp, SSH_KEYSTROKE_CHAFF_MIN_MS + + arc4random_uniform(SSH_KEYSTROKE_CHAFF_RNG_MS)); + timespecadd(&now, &tmp, &chaff_until); + } + + ptimeout_deadline_monotime_tsp(timeout, &next_interval); + + if (just_started) + return 1; + + /* Don't arm output fd for poll until the timing interval has elapsed */ + if (timespeccmp(&now, &next_interval, <)) + return 0; + + /* Calculate number of intervals missed since the last check */ + n = (now.tv_sec - next_interval.tv_sec) * 1000LL * 1000 * 1000; + n += now.tv_nsec - next_interval.tv_nsec; + n /= options.obscure_keystroke_timing_interval * 1000LL * 1000; + n = (n < 0) ? 1 : n + 1; + + /* Advance to the next interval */ + ms_to_timespec(&tmp, options.obscure_keystroke_timing_interval * n); + timespecadd(&now, &tmp, &next_interval); + return 1; +} + /* * Waits until the client can do something (some data becomes available on * one of the file descriptors). @@ -508,7 +630,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, int *conn_in_readyp, int *conn_out_readyp) { struct timespec timeout; - int ret; + int ret, oready; u_int p; *conn_in_readyp = *conn_out_readyp = 0; @@ -528,11 +650,14 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, return; } + oready = obfuscate_keystroke_timing(ssh, &timeout); + /* Monitor server connection on reserved pollfd entries */ (*pfdp)[0].fd = connection_in; (*pfdp)[0].events = POLLIN; (*pfdp)[1].fd = connection_out; - (*pfdp)[1].events = ssh_packet_have_data_to_write(ssh) ? POLLOUT : 0; + (*pfdp)[1].events = (oready && ssh_packet_have_data_to_write(ssh)) ? + POLLOUT : 0; /* * Wait for something to happen. This will suspend the process until @@ -549,7 +674,7 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, ssh_packet_get_rekey_timeout(ssh)); } - ret = poll(*pfdp, *npfd_activep, ptimeout_get_ms(&timeout)); + ret = ppoll(*pfdp, *npfd_activep, ptimeout_get_tsp(&timeout), NULL); if (ret == -1) { /* diff --git a/usr.bin/ssh/kex.c b/usr.bin/ssh/kex.c index 615ede832..a414f962c 100644 --- a/usr.bin/ssh/kex.c +++ b/usr.bin/ssh/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.180 2023/08/21 21:16:18 tobhe Exp $ */ +/* $OpenBSD: kex.c,v 1.181 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -477,12 +477,14 @@ kex_send_ext_info(struct ssh *ssh) return SSH_ERR_ALLOC_FAIL; /* XXX filter algs list by allowed pubkey/hostbased types */ if ((r = sshpkt_start(ssh, SSH2_MSG_EXT_INFO)) != 0 || - (r = sshpkt_put_u32(ssh, 2)) != 0 || + (r = sshpkt_put_u32(ssh, 3)) != 0 || (r = sshpkt_put_cstring(ssh, "server-sig-algs")) != 0 || (r = sshpkt_put_cstring(ssh, algs)) != 0 || (r = sshpkt_put_cstring(ssh, "publickey-hostbound@openssh.com")) != 0 || (r = sshpkt_put_cstring(ssh, "0")) != 0 || + (r = sshpkt_put_cstring(ssh, "ping@openssh.com")) != 0 || + (r = sshpkt_put_cstring(ssh, "0")) != 0 || (r = sshpkt_send(ssh)) != 0) { error_fr(r, "compose"); goto out; @@ -512,6 +514,23 @@ kex_send_newkeys(struct ssh *ssh) return 0; } +/* Check whether an ext_info value contains the expected version string */ +static int +kex_ext_info_check_ver(struct kex *kex, const char *name, + const u_char *val, size_t len, const char *want_ver, u_int flag) +{ + if (memchr(val, '\0', len) != NULL) { + error("SSH2_MSG_EXT_INFO: %s value contains nul byte", name); + return SSH_ERR_INVALID_FORMAT; + } + debug_f("%s=<%s>", name, val); + if (strcmp(val, want_ver) == 0) + kex->flags |= flag; + else + debug_f("unsupported version of %s extension", name); + return 0; +} + int kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) { @@ -542,6 +561,8 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) /* Ensure no \0 lurking in value */ if (memchr(val, '\0', vlen) != NULL) { error_f("nul byte in %s", name); + free(name); + free(val); return SSH_ERR_INVALID_FORMAT; } debug_f("%s=<%s>", name, val); @@ -549,18 +570,18 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh) val = NULL; } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { - /* XXX refactor */ - /* Ensure no \0 lurking in value */ - if (memchr(val, '\0', vlen) != NULL) { - error_f("nul byte in %s", name); - return SSH_ERR_INVALID_FORMAT; + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PUBKEY_HOSTBOUND)) != 0) { + free(name); + free(val); + return r; } - debug_f("%s=<%s>", name, val); - if (strcmp(val, "0") == 0) - kex->flags |= KEX_HAS_PUBKEY_HOSTBOUND; - else { - debug_f("unsupported version of %s extension", - name); + } else if (strcmp(name, "ping@openssh.com") == 0) { + if ((r = kex_ext_info_check_ver(kex, name, val, vlen, + "0", KEX_HAS_PING)) != 0) { + free(name); + free(val); + return r; } } else debug_f("%s (unrecognised)", name); diff --git a/usr.bin/ssh/kex.h b/usr.bin/ssh/kex.h index 1a7584558..50738b18d 100644 --- a/usr.bin/ssh/kex.h +++ b/usr.bin/ssh/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.118 2023/03/06 12:14:48 dtucker Exp $ */ +/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -104,6 +104,7 @@ enum kex_exchange { #define KEX_HAS_PUBKEY_HOSTBOUND 0x0004 #define KEX_RSA_SHA2_256_SUPPORTED 0x0008 /* only set in server for now */ #define KEX_RSA_SHA2_512_SUPPORTED 0x0010 /* only set in server for now */ +#define KEX_HAS_PING 0x0020 struct sshenc { char *name; diff --git a/usr.bin/ssh/misc.c b/usr.bin/ssh/misc.c index 52d79e748..59ee9c9ed 100644 --- a/usr.bin/ssh/misc.c +++ b/usr.bin/ssh/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.186 2023/08/18 01:37:41 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.187 2023/08/28 03:31:16 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -2792,22 +2792,33 @@ ptimeout_deadline_ms(struct timespec *pt, long ms) ptimeout_deadline_tsp(pt, &p); } +/* Specify a poll/ppoll deadline at wall clock monotime 'when' (timespec) */ +void +ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when) +{ + struct timespec now, t; + + monotime_ts(&now); + + if (timespeccmp(&now, when, >=)) { + /* 'when' is now or in the past. Timeout ASAP */ + pt->tv_sec = 0; + pt->tv_nsec = 0; + } else { + timespecsub(when, &now, &t); + ptimeout_deadline_tsp(pt, &t); + } +} + /* Specify a poll/ppoll deadline at wall clock monotime 'when' */ void ptimeout_deadline_monotime(struct timespec *pt, time_t when) { - struct timespec now, t; + struct timespec t; t.tv_sec = when; t.tv_nsec = 0; - monotime_ts(&now); - - if (timespeccmp(&now, &t, >=)) - ptimeout_deadline_sec(pt, 0); - else { - timespecsub(&t, &now, &t); - ptimeout_deadline_tsp(pt, &t); - } + ptimeout_deadline_monotime_tsp(pt, &t); } /* Get a poll(2) timeout value in milliseconds */ diff --git a/usr.bin/ssh/misc.h b/usr.bin/ssh/misc.h index 0221ce24e..5e0c45277 100644 --- a/usr.bin/ssh/misc.h +++ b/usr.bin/ssh/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.104 2023/08/18 01:37:41 djm Exp $ */ +/* $OpenBSD: misc.h,v 1.105 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen @@ -212,6 +212,7 @@ struct timespec; void ptimeout_init(struct timespec *pt); void ptimeout_deadline_sec(struct timespec *pt, long sec); void ptimeout_deadline_ms(struct timespec *pt, long ms); +void ptimeout_deadline_monotime_tsp(struct timespec *pt, struct timespec *when); void ptimeout_deadline_monotime(struct timespec *pt, time_t when); int ptimeout_get_ms(struct timespec *pt); struct timespec *ptimeout_get_tsp(struct timespec *pt); diff --git a/usr.bin/ssh/packet.c b/usr.bin/ssh/packet.c index b9a5343bf..0ef57d4e9 100644 --- a/usr.bin/ssh/packet.c +++ b/usr.bin/ssh/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.310 2023/04/06 03:21:31 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.312 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1034,6 +1034,8 @@ int ssh_packet_log_type(u_char type) { switch (type) { + case SSH2_MSG_PING: + case SSH2_MSG_PONG: case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: @@ -1654,7 +1656,7 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) goto out; if (ssh_packet_log_type(*typep)) debug3("receive packet: type %u", *typep); - if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) { + if (*typep < SSH2_MSG_MIN) { if ((r = sshpkt_disconnect(ssh, "Invalid ssh2 packet type: %d", *typep)) != 0 || (r = ssh_packet_write_wait(ssh)) != 0) @@ -1689,6 +1691,8 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) u_int reason, seqnr; int r; u_char *msg; + const u_char *d; + size_t len; for (;;) { msg = NULL; @@ -1732,6 +1736,21 @@ ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) debug("Received SSH2_MSG_UNIMPLEMENTED for %u", seqnr); break; + case SSH2_MSG_PING: + if ((r = sshpkt_get_string_direct(ssh, &d, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PING len %zu", len)); + if ((r = sshpkt_start(ssh, SSH2_MSG_PONG)) != 0 || + (r = sshpkt_put_string(ssh, d, len)) != 0 || + (r = sshpkt_send(ssh)) != 0) + return r; + break; + case SSH2_MSG_PONG: + if ((r = sshpkt_get_string_direct(ssh, + NULL, &len)) != 0) + return r; + DBG(debug("Received SSH2_MSG_PONG len %zu", len)); + break; default: return 0; } @@ -2041,6 +2060,18 @@ ssh_packet_not_very_much_data_to_write(struct ssh *ssh) return sshbuf_len(ssh->state->output) < 128 * 1024; } +/* + * returns true when there are at most a few keystrokes of data to write + * and the connection is in interactive mode. + */ + +int +ssh_packet_interactive_data_to_write(struct ssh *ssh) +{ + return ssh->state->interactive_mode && + sshbuf_len(ssh->state->output) < 256; +} + void ssh_packet_set_tos(struct ssh *ssh, int tos) { diff --git a/usr.bin/ssh/packet.h b/usr.bin/ssh/packet.h index c8817ffa2..39ee0b5ff 100644 --- a/usr.bin/ssh/packet.h +++ b/usr.bin/ssh/packet.h @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.h,v 1.94 2022/01/22 00:49:34 djm Exp $ */ +/* $OpenBSD: packet.h,v 1.95 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen @@ -139,6 +139,7 @@ int ssh_packet_write_poll(struct ssh *); int ssh_packet_write_wait(struct ssh *); int ssh_packet_have_data_to_write(struct ssh *); int ssh_packet_not_very_much_data_to_write(struct ssh *); +int ssh_packet_interactive_data_to_write(struct ssh *); int ssh_packet_connection_is_on_socket(struct ssh *); int ssh_packet_remaining(struct ssh *); diff --git a/usr.bin/ssh/readconf.c b/usr.bin/ssh/readconf.c index ed1199811..2d85d48a3 100644 --- a/usr.bin/ssh/readconf.c +++ b/usr.bin/ssh/readconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.c,v 1.380 2023/07/17 06:16:33 djm Exp $ */ +/* $OpenBSD: readconf.c,v 1.381 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -162,7 +162,7 @@ typedef enum { oFingerprintHash, oUpdateHostkeys, oHostbasedAcceptedAlgorithms, oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump, oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize, - oEnableEscapeCommandline, + oEnableEscapeCommandline, oObscureKeystrokeTiming, oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported } OpCodes; @@ -311,6 +311,7 @@ static struct { { "knownhostscommand", oKnownHostsCommand }, { "requiredrsasize", oRequiredRSASize }, { "enableescapecommandline", oEnableEscapeCommandline }, + { "obscurekeystroketiming", oObscureKeystrokeTiming }, { NULL, oBadOption } }; @@ -2257,6 +2258,48 @@ parse_pubkey_algos: intptr = &options->required_rsa_size; goto parse_int; + case oObscureKeystrokeTiming: + value = -1; + while ((arg = argv_next(&ac, &av)) != NULL) { + if (value != -1) { + error("%s line %d: invalid arguments", + filename, linenum); + goto out; + } + if (strcmp(arg, "yes") == 0 || + strcmp(arg, "true") == 0) + value = SSH_KEYSTROKE_DEFAULT_INTERVAL_MS; + else if (strcmp(arg, "no") == 0 || + strcmp(arg, "false") == 0) + value = 0; + else if (strncmp(arg, "interval:", 9) == 0) { + if ((errstr = atoi_err(arg + 9, + &value)) != NULL) { + error("%s line %d: integer value %s.", + filename, linenum, errstr); + goto out; + } + if (value <= 0 || value > 1000) { + error("%s line %d: value out of range.", + filename, linenum); + goto out; + } + } else { + error("%s line %d: unsupported argument \"%s\"", + filename, linenum, arg); + goto out; + } + } + if (value == -1) { + error("%s line %d: missing argument", + filename, linenum); + goto out; + } + intptr = &options->obscure_keystroke_timing_interval; + if (*activep && *intptr == -1) + *intptr = value; + break; + case oDeprecated: debug("%s line %d: Deprecated option \"%s\"", filename, linenum, keyword); @@ -2507,6 +2550,7 @@ initialize_options(Options * options) options->known_hosts_command = NULL; options->required_rsa_size = -1; options->enable_escape_commandline = -1; + options->obscure_keystroke_timing_interval = -1; options->tag = NULL; } @@ -2701,6 +2745,10 @@ fill_default_options(Options * options) options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE; if (options->enable_escape_commandline == -1) options->enable_escape_commandline = 0; + if (options->obscure_keystroke_timing_interval == -1) { + options->obscure_keystroke_timing_interval = + SSH_KEYSTROKE_DEFAULT_INTERVAL_MS; + } /* Expand KEX name lists */ all_cipher = cipher_alg_list(',', 0); @@ -3243,6 +3291,16 @@ lookup_opcode_name(OpCodes code) static void dump_cfg_int(OpCodes code, int val) { + if (code == oObscureKeystrokeTiming) { + if (val == 0) { + printf("%s no\n", lookup_opcode_name(code)); + return; + } else if (val == SSH_KEYSTROKE_DEFAULT_INTERVAL_MS) { + printf("%s yes\n", lookup_opcode_name(code)); + return; + } + /* FALLTHROUGH */ + } printf("%s %d\n", lookup_opcode_name(code), val); } @@ -3393,6 +3451,8 @@ dump_client_config(Options *o, const char *host) dump_cfg_int(oServerAliveCountMax, o->server_alive_count_max); dump_cfg_int(oServerAliveInterval, o->server_alive_interval); dump_cfg_int(oRequiredRSASize, o->required_rsa_size); + dump_cfg_int(oObscureKeystrokeTiming, + o->obscure_keystroke_timing_interval); /* String options */ dump_cfg_string(oBindAddress, o->bind_address); diff --git a/usr.bin/ssh/readconf.h b/usr.bin/ssh/readconf.h index dfe5bab0a..ce261bd63 100644 --- a/usr.bin/ssh/readconf.h +++ b/usr.bin/ssh/readconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: readconf.h,v 1.151 2023/07/17 04:08:31 djm Exp $ */ +/* $OpenBSD: readconf.h,v 1.152 2023/08/28 03:31:16 djm Exp $ */ /* * Author: Tatu Ylonen @@ -180,6 +180,7 @@ typedef struct { int required_rsa_size; /* minimum size of RSA keys */ int enable_escape_commandline; /* ~C commandline */ + int obscure_keystroke_timing_interval; char *ignored_unknown; /* Pattern list of unknown tokens to ignore */ } Options; @@ -222,6 +223,11 @@ typedef struct { #define SSH_STRICT_HOSTKEY_YES 2 #define SSH_STRICT_HOSTKEY_ASK 3 +/* ObscureKeystrokes parameters */ +#define SSH_KEYSTROKE_DEFAULT_INTERVAL_MS 20 +#define SSH_KEYSTROKE_CHAFF_MIN_MS 1024 +#define SSH_KEYSTROKE_CHAFF_RNG_MS 2048 + const char *kex_default_pk_alg(void); char *ssh_connection_hash(const char *thishost, const char *host, const char *portstr, const char *user); diff --git a/usr.bin/ssh/ssh2.h b/usr.bin/ssh/ssh2.h index 288c39f69..0d48d0527 100644 --- a/usr.bin/ssh/ssh2.h +++ b/usr.bin/ssh/ssh2.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh2.h,v 1.20 2023/08/14 03:37:00 djm Exp $ */ +/* $OpenBSD: ssh2.h,v 1.21 2023/08/28 03:28:43 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -108,6 +108,10 @@ #define SSH2_MSG_KEX_ECDH_INIT 30 #define SSH2_MSG_KEX_ECDH_REPLY 31 +/* transport layer: OpenSSH extensions */ +#define SSH2_MSG_PING 192 +#define SSH2_MSG_PONG 193 + /* user authentication: generic */ #define SSH2_MSG_USERAUTH_REQUEST 50 diff --git a/usr.bin/ssh/ssh_config.5 b/usr.bin/ssh/ssh_config.5 index 1eae6e726..6f526b697 100644 --- a/usr.bin/ssh/ssh_config.5 +++ b/usr.bin/ssh/ssh_config.5 @@ -33,8 +33,8 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.383 2023/07/17 05:36:14 jsg Exp $ -.Dd $Mdocdate: July 17 2023 $ +.\" $OpenBSD: ssh_config.5,v 1.386 2023/08/28 09:52:09 djm Exp $ +.Dd $Mdocdate: August 28 2023 $ .Dt SSH_CONFIG 5 .Os .Sh NAME @@ -1359,6 +1359,25 @@ or Specifies the number of password prompts before giving up. The argument to this keyword must be an integer. The default is 3. +.It Cm ObscureKeystrokeTiming +Specifies whether +.Xr ssh 1 +should try to obscure inter-keystroke timings from passive observers of +network traffic. +If enabled, then for interactive sessions, +.Xr ssh 1 +will send keystrokes at fixed intervals of a few tens of milliseconds +and will send fake keystroke packets for some time after typing ceases. +The argument to this keyword must be +.Cm yes , +.Cm no +or an interval specifier of the form +.Cm interval:milliseconds +(e.g.\& +.Cm interval:80 +for 80 milliseconds). +The default is to obscure keystrokes using a 20ms packet interval. +Note that smaller intervals will result in higher fake keystroke packet rates. .It Cm PasswordAuthentication Specifies whether to use password authentication. The argument to this keyword must be