version 1.392, 2023/04/03 08:10:54 |
version 1.393, 2023/08/28 03:31:16 |
|
|
schedule_server_alive_check(); |
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) * 1000 * 1000 * 1000; |
|
n += now.tv_nsec - next_interval.tv_nsec; |
|
n /= options.obscure_keystroke_timing_interval * 1000 * 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 |
* Waits until the client can do something (some data becomes available on |
* one of the file descriptors). |
* one of the file descriptors). |
*/ |
*/ |
|
|
int *conn_in_readyp, int *conn_out_readyp) |
int *conn_in_readyp, int *conn_out_readyp) |
{ |
{ |
struct timespec timeout; |
struct timespec timeout; |
int ret; |
int ret, oready; |
u_int p; |
u_int p; |
|
|
*conn_in_readyp = *conn_out_readyp = 0; |
*conn_in_readyp = *conn_out_readyp = 0; |
|
|
return; |
return; |
} |
} |
|
|
|
oready = obfuscate_keystroke_timing(ssh, &timeout); |
|
|
/* Monitor server connection on reserved pollfd entries */ |
/* Monitor server connection on reserved pollfd entries */ |
(*pfdp)[0].fd = connection_in; |
(*pfdp)[0].fd = connection_in; |
(*pfdp)[0].events = POLLIN; |
(*pfdp)[0].events = POLLIN; |
(*pfdp)[1].fd = connection_out; |
(*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 |
* Wait for something to happen. This will suspend the process until |
|
|
ssh_packet_get_rekey_timeout(ssh)); |
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) { |
if (ret == -1) { |
/* |
/* |