diff options
| author | Ed Maste <emaste@FreeBSD.org> | 2023-10-04 12:06:41 +0000 |
|---|---|---|
| committer | Ed Maste <emaste@FreeBSD.org> | 2023-10-04 12:06:41 +0000 |
| commit | b3ced7f26826767e378a5970a65d875fc1cad61e (patch) | |
| tree | ef449f71f51d75e39e1e27fc5004afaefb75d805 /clientloop.c | |
| parent | 78f30535bcdb64cf743b416327ecb0f00e25b2aa (diff) | |
Vendor import of OpenSSH 9.5p1vendor/openssh/9.5p1
Diffstat (limited to 'clientloop.c')
| -rw-r--r-- | clientloop.c | 189 |
1 files changed, 180 insertions, 9 deletions
diff --git a/clientloop.c b/clientloop.c index 99846a978397..3e9fa3220b7d 100644 --- a/clientloop.c +++ b/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.398 2023/09/10 03:51:55 djm Exp $ */ /* * Author: Tatu Ylonen <ylo@cs.hut.fi> * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland @@ -118,6 +118,9 @@ /* Permitted RSA signature algorithms for UpdateHostkeys proofs */ #define HOSTKEY_PROOF_RSA_ALGS "rsa-sha2-512,rsa-sha2-256" +/* Uncertainty (in percent) of keystroke timing intervals */ +#define SSH_KEYSTROKE_TIMING_FUZZ 10 + /* import options */ extern Options options; @@ -507,17 +510,181 @@ 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; +} + +/* Sets the next interval to send a keystroke or chaff packet */ +static void +set_next_interval(const struct timespec *now, struct timespec *next_interval, + u_int interval_ms, int starting) +{ + struct timespec tmp; + long long interval_ns, fuzz_ns; + static long long rate_fuzz; + + interval_ns = interval_ms * (1000LL * 1000); + fuzz_ns = (interval_ns * SSH_KEYSTROKE_TIMING_FUZZ) / 100; + /* Center fuzz around requested interval */ + if (fuzz_ns > INT_MAX) + fuzz_ns = INT_MAX; + if (fuzz_ns > interval_ns) { + /* Shouldn't happen */ + fatal_f("internal error: fuzz %u%% %lldns > interval %lldns", + SSH_KEYSTROKE_TIMING_FUZZ, fuzz_ns, interval_ns); + } + /* + * Randomise the keystroke/chaff intervals in two ways: + * 1. Each interval has some random jitter applied to make the + * interval-to-interval time unpredictable. + * 2. The overall interval rate is also randomly perturbed for each + * chaffing session to make the average rate unpredictable. + */ + if (starting) + rate_fuzz = arc4random_uniform(fuzz_ns); + interval_ns -= fuzz_ns; + interval_ns += arc4random_uniform(fuzz_ns) + rate_fuzz; + + tmp.tv_sec = interval_ns / (1000 * 1000 * 1000); + tmp.tv_nsec = interval_ns % (1000 * 1000 * 1000); + + timespecadd(now, &tmp, next_interval); +} + +/* + * 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, + int channel_did_enqueue) +{ + 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 && channel_did_enqueue && + 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) { + if (active) { + debug3_f("stopping: %s (%llu chaff packets sent)", + stop_reason, nchaff); + active = 0; + } + 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) && + channel_did_enqueue && ssh_packet_have_data_to_write(ssh)) { + debug3_f("starting: interval ~%dms", + options.obscure_keystroke_timing_interval); + just_started = had_keystroke = active = 1; + nchaff = 0; + set_next_interval(&now, &next_interval, + options.obscure_keystroke_timing_interval, 1); + } + + /* 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 */ + set_next_interval(&now, &next_interval, + options.obscure_keystroke_timing_interval * n, 0); + return 1; +} + /* * Waits until the client can do something (some data becomes available on * one of the file descriptors). */ static void client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, - u_int *npfd_allocp, u_int *npfd_activep, int rekeying, + u_int *npfd_allocp, u_int *npfd_activep, int channel_did_enqueue, 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; @@ -537,11 +704,14 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, return; } + oready = obfuscate_keystroke_timing(ssh, &timeout, channel_did_enqueue); + /* 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 @@ -553,12 +723,12 @@ client_wait_until_can_do_something(struct ssh *ssh, struct pollfd **pfdp, ptimeout_deadline_monotime(&timeout, control_persist_exit_time); if (options.server_alive_interval > 0) ptimeout_deadline_monotime(&timeout, server_alive_time); - if (options.rekey_interval > 0 && !rekeying) { + if (options.rekey_interval > 0 && !ssh_packet_is_rekeying(ssh)) { ptimeout_deadline_sec(&timeout, 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) { /* @@ -1275,7 +1445,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, struct pollfd *pfd = NULL; u_int npfd_alloc = 0, npfd_active = 0; double start_time, total_time; - int r, len; + int channel_did_enqueue = 0, r, len; u_int64_t ibytes, obytes; int conn_in_ready, conn_out_ready; @@ -1365,6 +1535,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, /* Main loop of the client for the interactive session mode. */ while (!quit_pending) { + channel_did_enqueue = 0; /* Process buffered packets sent by the server. */ client_process_buffered_input_packets(ssh); @@ -1386,7 +1557,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, * enqueue them for sending to the server. */ if (ssh_packet_not_very_much_data_to_write(ssh)) - channel_output_poll(ssh); + channel_did_enqueue = channel_output_poll(ssh); /* * Check if the window size has changed, and buffer a @@ -1402,7 +1573,7 @@ client_loop(struct ssh *ssh, int have_pty, int escape_char_arg, * available on one of the descriptors). */ client_wait_until_can_do_something(ssh, &pfd, &npfd_alloc, - &npfd_active, ssh_packet_is_rekeying(ssh), + &npfd_active, channel_did_enqueue, &conn_in_ready, &conn_out_ready); if (quit_pending) |
