1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Use a bufchain for outgoing SSH wire data.

This mirrors the use of one for incoming wire data: now when we send
raw data (be it the initial greeting, or the output of binary packet
construction), we put it on ssh->outgoing_data, and schedule a
callback to transfer that into the socket.

Partly this is in preparation for delegating the task of appending to
that bufchain to a separate self-contained module that won't have
direct access to the connection's Socket. But also, it has the very
nice feature that I get to throw away the ssh_pkt_defer system
completely! That was there so that we could construct more than one
packet in rapid succession, concatenate them into a single blob, and
pass that blob to the socket in one go so that the TCP headers
couldn't contain any trace of where the boundary between them was. But
now we don't need a separate function to do that: calling the ordinary
packet-send routine twice in the same function before returning to the
main event loop will have that effect _anyway_.
This commit is contained in:
Simon Tatham 2018-06-09 09:09:10 +01:00
parent ba7571291a
commit 9e3522a971

257
ssh.c
View File

@ -793,8 +793,6 @@ struct ssh_tag {
PktOut **queue;
int queuelen, queuesize;
int queueing;
unsigned char *deferred_send_data;
int deferred_len, deferred_size;
/*
* Gross hack: pscp will try to start SFTP but fall back to
@ -858,6 +856,9 @@ struct ssh_tag {
bufchain user_input;
struct IdempotentCallback user_input_consumer;
bufchain outgoing_data;
struct IdempotentCallback outgoing_data_sender;
const char *rekey_reason;
enum RekeyClass rekey_class;
@ -926,7 +927,7 @@ struct ssh_tag {
* Track incoming and outgoing data sizes and time, for
* size-based rekeys.
*/
unsigned long incoming_data_size, outgoing_data_size, deferred_data_size;
unsigned long incoming_data_size, outgoing_data_size;
unsigned long max_data_size;
int kex_in_progress;
unsigned long next_rekey, last_rekey;
@ -2059,28 +2060,11 @@ static int s_write(Ssh ssh, const void *data, int len)
}
static void s_wrpkt(Ssh ssh, PktOut *pkt)
{
int len, backlog, offset;
len = s_wrpkt_prepare(ssh, pkt, &offset);
backlog = s_write(ssh, pkt->data + offset, len);
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
ssh_free_pktout(pkt);
}
static void s_wrpkt_defer(Ssh ssh, PktOut *pkt)
{
int len, offset;
len = s_wrpkt_prepare(ssh, pkt, &offset);
if (ssh->deferred_len + len > ssh->deferred_size) {
ssh->deferred_size = ssh->deferred_len + len + 128;
ssh->deferred_send_data = sresize(ssh->deferred_send_data,
ssh->deferred_size,
unsigned char);
}
memcpy(ssh->deferred_send_data + ssh->deferred_len,
pkt->data + offset, len);
ssh->deferred_len += len;
bufchain_add(&ssh->outgoing_data, pkt->data + offset, len);
queue_idempotent_callback(&ssh->outgoing_data_sender);
ssh_free_pktout(pkt);
}
@ -2141,16 +2125,6 @@ static void send_packet(Ssh ssh, int pkttype, ...)
s_wrpkt(ssh, pkt);
}
static void defer_packet(Ssh ssh, int pkttype, ...)
{
PktOut *pkt;
va_list ap;
va_start(ap, pkttype);
pkt = construct_packet(ssh, pkttype, ap);
va_end(ap);
s_wrpkt_defer(ssh, pkt);
}
static int ssh_versioncmp(const char *a, const char *b)
{
char *ae, *be;
@ -2320,62 +2294,29 @@ static ptrlen ssh2_pkt_construct(Ssh ssh, PktOut *pkt)
}
/*
* Routines called from the main SSH code to send packets. There
* are quite a few of these, because we have two separate
* mechanisms for delaying the sending of packets:
*
* - In order to send an IGNORE message and a password message in
* a single fixed-length blob, we require the ability to
* concatenate the encrypted forms of those two packets _into_ a
* single blob and then pass it to our <network.h> transport
* layer in one go. Hence, there's a deferment mechanism which
* works after packet encryption.
*
* - In order to avoid sending any connection-layer messages
* during repeat key exchange, we have to queue up any such
* outgoing messages _before_ they are encrypted (and in
* particular before they're allocated sequence numbers), and
* then send them once we've finished.
*
* I call these mechanisms `defer' and `queue' respectively, so as
* to distinguish them reasonably easily.
*
* The functions send_noqueue() and defer_noqueue() free the packet
* structure they are passed. Every outgoing packet goes through
* precisely one of these functions in its life; packets passed to
* ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of
* these or get queued, and then when the queue is later emptied
* the packets are all passed to defer_noqueue().
*
* When using a CBC-mode cipher, it's necessary to ensure that an
* attacker can't provide data to be encrypted using an IV that they
* know. We ensure this by prefixing each packet that might contain
* user data with an SSH_MSG_IGNORE. This is done using the deferral
* mechanism, so in this case send_noqueue() ends up redirecting to
* defer_noqueue(). If you don't like this inefficiency, don't use
* CBC.
*/
static void ssh2_pkt_defer_noqueue(Ssh, PktOut *, int);
static void ssh_pkt_defersend(Ssh);
/*
* Send an SSH-2 packet immediately, without queuing or deferring.
* Send an SSH-2 packet immediately, without queuing.
*/
static void ssh2_pkt_send_noqueue(Ssh ssh, PktOut *pkt)
{
ptrlen data;
int backlog;
if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) {
/* We need to send two packets, so use the deferral mechanism. */
ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
ssh_pkt_defersend(ssh);
return;
if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
bufchain_size(&ssh->outgoing_data) != 0 &&
!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
/*
* When using a CBC-mode cipher in SSH-2, it's necessary to
* ensure that an attacker can't provide data to be encrypted
* using an IV that they know. We ensure this by prefixing
* each packet that might contain user data with an
* SSH_MSG_IGNORE.
*/
PktOut *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
put_stringz(ipkt, "");
data = ssh2_pkt_construct(ssh, ipkt);
bufchain_add(&ssh->outgoing_data, data.ptr, data.len);
}
data = ssh2_pkt_construct(ssh, pkt);
backlog = s_write(ssh, data.ptr, data.len);
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
bufchain_add(&ssh->outgoing_data, data.ptr, data.len);
queue_idempotent_callback(&ssh->outgoing_data_sender);
ssh->outgoing_data_size += pkt->encrypted_len;
if (!ssh->kex_in_progress &&
@ -2390,36 +2331,6 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, PktOut *pkt)
ssh_free_pktout(pkt);
}
/*
* Defer an SSH-2 packet.
*/
static void ssh2_pkt_defer_noqueue(Ssh ssh, PktOut *pkt, int noignore)
{
ptrlen data;
if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
ssh->deferred_len == 0 && !noignore &&
!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
/*
* Interpose an SSH_MSG_IGNORE to ensure that user data don't
* get encrypted with a known IV.
*/
PktOut *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
put_stringz(ipkt, "");
ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
}
data = ssh2_pkt_construct(ssh, pkt);
if (ssh->deferred_len + data.len > ssh->deferred_size) {
ssh->deferred_size = ssh->deferred_len + data.len + 128;
ssh->deferred_send_data = sresize(ssh->deferred_send_data,
ssh->deferred_size,
unsigned char);
}
memcpy(ssh->deferred_send_data + ssh->deferred_len, data.ptr, data.len);
ssh->deferred_len += data.len;
ssh->deferred_data_size += pkt->encrypted_len;
ssh_free_pktout(pkt);
}
/*
* Queue an SSH-2 packet.
*/
@ -2441,58 +2352,38 @@ static void ssh2_pkt_queue(Ssh ssh, PktOut *pkt)
*/
static void ssh2_pkt_send(Ssh ssh, PktOut *pkt)
{
if (ssh->queueing)
if (ssh->queueing) {
ssh2_pkt_queue(ssh, pkt);
else
} else {
ssh2_pkt_send_noqueue(ssh, pkt);
}
}
/*
* Either queue or defer a packet, depending on whether queueing is
* set.
*/
static void ssh2_pkt_defer(Ssh ssh, PktOut *pkt)
static void ssh_send_outgoing_data(void *ctx)
{
if (ssh->queueing)
ssh2_pkt_queue(ssh, pkt);
else
ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
}
Ssh ssh = (Ssh)ctx;
/*
* Send the whole deferred data block constructed by
* ssh2_pkt_defer() or SSH-1's defer_packet().
*
* The expected use of the defer mechanism is that you call
* ssh2_pkt_defer() a few times, then call ssh_pkt_defersend(). If
* not currently queueing, this simply sets up deferred_send_data
* and then sends it. If we _are_ currently queueing, the calls to
* ssh2_pkt_defer() put the deferred packets on to the queue
* instead, and therefore ssh_pkt_defersend() has no deferred data
* to send. Hence, there's no need to make it conditional on
* ssh->queueing.
*/
static void ssh_pkt_defersend(Ssh ssh)
{
int backlog;
backlog = s_write(ssh, ssh->deferred_send_data, ssh->deferred_len);
ssh->deferred_len = ssh->deferred_size = 0;
sfree(ssh->deferred_send_data);
ssh->deferred_send_data = NULL;
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
while (bufchain_size(&ssh->outgoing_data) > 0) {
void *data;
int len, backlog;
if (ssh->version == 2) {
ssh->outgoing_data_size += ssh->deferred_data_size;
ssh->deferred_data_size = 0;
if (!ssh->kex_in_progress &&
!ssh->bare_connection &&
ssh->max_data_size != 0 &&
bufchain_prefix(&ssh->outgoing_data, &data, &len);
backlog = s_write(ssh, data, len);
bufchain_consume(&ssh->outgoing_data, len);
ssh->outgoing_data_size += len;
if (ssh->version == 2 && !ssh->kex_in_progress &&
!ssh->bare_connection && ssh->max_data_size != 0 &&
ssh->outgoing_data_size > ssh->max_data_size) {
ssh->rekey_reason = "too much data sent";
ssh->rekey_class = RK_NORMAL;
queue_idempotent_callback(&ssh->ssh2_transport_icb);
}
if (backlog > SSH_MAX_BACKLOG) {
ssh_throttle_all(ssh, 1, backlog);
return;
}
}
}
@ -2518,26 +2409,27 @@ static void ssh2_pkt_send_with_padding(Ssh ssh, PktOut *pkt, int padsize)
#endif
{
/*
* If we can't do that, however, an alternative approach is
* to use the pkt_defer mechanism to bundle the packet
* tightly together with an SSH_MSG_IGNORE such that their
* combined length is a constant. So first we construct the
* final form of this packet and defer its sending.
* If we can't do that, however, an alternative approach is to
* bundle the packet tightly together with an SSH_MSG_IGNORE
* such that their combined length is a constant. So first we
* construct the final form of this packet and append it to
* the outgoing_data bufchain...
*/
ssh2_pkt_defer(ssh, pkt);
ssh2_pkt_send(ssh, pkt);
/*
* Now construct an SSH_MSG_IGNORE which includes a string
* that's an exact multiple of the cipher block size. (If
* the cipher is NULL so that the block size is
* unavailable, we don't do this trick at all, because we
* gain nothing by it.)
* ... but before we return from this function (triggering a
* call to the outgoing_data_sender), we also construct an
* SSH_MSG_IGNORE which includes a string that's an exact
* multiple of the cipher block size. (If the cipher is NULL
* so that the block size is unavailable, we don't do this
* trick at all, because we gain nothing by it.)
*/
if (ssh->cscipher &&
!(ssh->remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
int stringlen, i;
stringlen = (256 - ssh->deferred_len);
stringlen = (256 - bufchain_size(&ssh->outgoing_data));
stringlen += ssh->cscipher->blksize - 1;
stringlen -= (stringlen % ssh->cscipher->blksize);
if (ssh->cscomp) {
@ -2559,16 +2451,13 @@ static void ssh2_pkt_send_with_padding(Ssh ssh, PktOut *pkt, int padsize)
put_byte(substr, random_byte());
put_stringsb(pkt, substr);
}
ssh2_pkt_defer(ssh, pkt);
ssh2_pkt_send(ssh, pkt);
}
ssh_pkt_defersend(ssh);
}
}
/*
* Send all queued SSH-2 packets. We send them by means of
* ssh2_pkt_defer_noqueue(), in case they included a pair of
* packets that needed to be lumped together.
* Send all queued SSH-2 packets.
*/
static void ssh2_pkt_queuesend(Ssh ssh)
{
@ -2577,10 +2466,8 @@ static void ssh2_pkt_queuesend(Ssh ssh)
assert(!ssh->queueing);
for (i = 0; i < ssh->queuelen; i++)
ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE);
ssh2_pkt_send_noqueue(ssh, ssh->queue[i]);
ssh->queuelen = 0;
ssh_pkt_defersend(ssh);
}
#if 0
@ -2913,7 +2800,8 @@ static void ssh_send_verstring(Ssh ssh, const char *protoname, char *svers)
logeventf(ssh, "We claim version: %.*s",
strcspn(verstring, "\015\012"), verstring);
s_write(ssh, verstring, strlen(verstring));
bufchain_add(&ssh->outgoing_data, verstring, strlen(verstring));
queue_idempotent_callback(&ssh->outgoing_data_sender);
sfree(verstring);
}
@ -4826,9 +4714,9 @@ static void do_ssh1_login(void *vctx)
for (i = bottom; i <= top; i++) {
if (i == pwlen) {
defer_packet(ssh, s->pwpkt_type,
PKT_STR,s->cur_prompt->prompts[0]->result,
PKT_END);
send_packet(ssh, s->pwpkt_type,
PKT_STR, s->cur_prompt->prompts[0]->result,
PKT_END);
} else {
for (j = 0; j < i; j++) {
do {
@ -4836,12 +4724,11 @@ static void do_ssh1_login(void *vctx)
} while (randomstr[j] == '\0');
}
randomstr[i] = '\0';
defer_packet(ssh, SSH1_MSG_IGNORE,
PKT_STR, randomstr, PKT_END);
send_packet(ssh, SSH1_MSG_IGNORE,
PKT_STR, randomstr, PKT_END);
}
}
logevent("Sending password with camouflage packets");
ssh_pkt_defersend(ssh);
sfree(randomstr);
}
else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
@ -11898,9 +11785,6 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->eof_needed = FALSE;
ssh->ldisc = NULL;
ssh->logctx = NULL;
ssh->deferred_send_data = NULL;
ssh->deferred_len = 0;
ssh->deferred_size = 0;
ssh->fallback_cmd = 0;
ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
@ -11954,6 +11838,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->user_input_consumer.fn = ssh_process_user_input;
ssh->user_input_consumer.ctx = ssh;
ssh->user_input_consumer.queued = FALSE;
bufchain_init(&ssh->outgoing_data);
ssh->outgoing_data_sender.fn = ssh_send_outgoing_data;
ssh->outgoing_data_sender.ctx = ssh;
ssh->outgoing_data_sender.queued = FALSE;
ssh->current_user_input_fn = NULL;
ssh->pending_newkeys = FALSE;
ssh->rekey_reason = NULL;
@ -12010,8 +11898,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->pinger = NULL;
ssh->incoming_data_size = ssh->outgoing_data_size =
ssh->deferred_data_size = 0L;
ssh->incoming_data_size = ssh->outgoing_data_size = 0L;
ssh->max_data_size = parse_blocksize(conf_get_str(ssh->conf,
CONF_ssh_rekey_data));
ssh->kex_in_progress = FALSE;
@ -12120,7 +12007,6 @@ static void ssh_free(void *handle)
freetree234(ssh->rportfwds);
ssh->rportfwds = NULL;
}
sfree(ssh->deferred_send_data);
if (ssh->x11disp)
x11_free_display(ssh->x11disp);
while ((auth = delpos234(ssh->x11authtree, 0)) != NULL)
@ -12132,6 +12018,7 @@ static void ssh_free(void *handle)
sfree(ssh->do_ssh2_userauth_state);
sfree(ssh->do_ssh2_connection_state);
bufchain_clear(&ssh->incoming_data);
bufchain_clear(&ssh->outgoing_data);
sfree(ssh->incoming_data_eof_message);
pq_clear(&ssh->pq_full);
pq_clear(&ssh->pq_ssh1_login);