diff options
Diffstat (limited to 'ssh/bpp2.c')
-rw-r--r-- | ssh/bpp2.c | 990 |
1 files changed, 990 insertions, 0 deletions
diff --git a/ssh/bpp2.c b/ssh/bpp2.c new file mode 100644 index 00000000..e019dd2e --- /dev/null +++ b/ssh/bpp2.c @@ -0,0 +1,990 @@ +/* + * Binary packet protocol for SSH-2. + */ + +#include <assert.h> + +#include "putty.h" +#include "ssh.h" +#include "bpp.h" +#include "sshcr.h" + +struct ssh2_bpp_direction { + unsigned long sequence; + ssh_cipher *cipher; + ssh2_mac *mac; + bool etm_mode; + const ssh_compression_alg *pending_compression; +}; + +struct ssh2_bpp_state { + int crState; + long len, pad, payload, packetlen, maclen, length, maxlen; + unsigned char *buf; + size_t bufsize; + unsigned char *data; + unsigned cipherblk; + PktIn *pktin; + struct DataTransferStats *stats; + bool cbc_ignore_workaround; + + struct ssh2_bpp_direction in, out; + /* comp and decomp logically belong in the per-direction + * substructure, except that they have different types */ + ssh_decompressor *in_decomp; + ssh_compressor *out_comp; + + bool is_server; + bool pending_newkeys; + bool pending_compression, seen_userauth_success; + bool enforce_next_packet_is_userauth_success; + unsigned nnewkeys; + int prev_type; + + BinaryPacketProtocol bpp; +}; + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp); +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp); +static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp); +static PktOut *ssh2_bpp_new_pktout(int type); + +static const BinaryPacketProtocolVtable ssh2_bpp_vtable = { + .free = ssh2_bpp_free, + .handle_input = ssh2_bpp_handle_input, + .handle_output = ssh2_bpp_handle_output, + .new_pktout = ssh2_bpp_new_pktout, + .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */ + .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ +}; + +BinaryPacketProtocol *ssh2_bpp_new( + LogContext *logctx, struct DataTransferStats *stats, bool is_server) +{ + struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); + memset(s, 0, sizeof(*s)); + s->bpp.vt = &ssh2_bpp_vtable; + s->bpp.logctx = logctx; + s->stats = stats; + s->is_server = is_server; + ssh_bpp_common_setup(&s->bpp); + return &s->bpp; +} + +static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) +{ + if (s->out.mac) + ssh2_mac_free(s->out.mac); + if (s->out.cipher) + ssh_cipher_free(s->out.cipher); + if (s->out_comp) + ssh_compressor_free(s->out_comp); +} + +static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s) +{ + /* As above, take care to free in.mac before in.cipher */ + if (s->in.mac) + ssh2_mac_free(s->in.mac); + if (s->in.cipher) + ssh_cipher_free(s->in.cipher); + if (s->in_decomp) + ssh_decompressor_free(s->in_decomp); +} + +static void ssh2_bpp_free(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + sfree(s->buf); + ssh2_bpp_free_outgoing_crypto(s); + ssh2_bpp_free_incoming_crypto(s); + sfree(s->pktin); + sfree(s); +} + +void ssh2_bpp_new_outgoing_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + ssh2_bpp_free_outgoing_crypto(s); + + if (cipher) { + s->out.cipher = ssh_cipher_new(cipher); + ssh_cipher_setkey(s->out.cipher, ckey); + ssh_cipher_setiv(s->out.cipher, iv); + + s->cbc_ignore_workaround = ( + (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) && + !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); + + bpp_logevent("Initialised %s outbound encryption", + ssh_cipher_alg(s->out.cipher)->text_name); + } else { + s->out.cipher = NULL; + s->cbc_ignore_workaround = false; + } + s->out.etm_mode = etm_mode; + if (mac) { + s->out.mac = ssh2_mac_new(mac, s->out.cipher); + /* + * Important that mac_setkey comes after cipher_setkey, + * because in the case where the MAC makes use of the cipher + * (e.g. AES-GCM), it will need the cipher to be keyed + * already. + */ + ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); + + bpp_logevent("Initialised %s outbound MAC algorithm%s%s", + ssh2_mac_text_name(s->out.mac), + etm_mode ? " (in ETM mode)" : "", + (s->out.cipher && + ssh_cipher_alg(s->out.cipher)->required_mac ? + " (required by cipher)" : "")); + } else { + s->out.mac = NULL; + } + + if (delayed_compression && !s->seen_userauth_success) { + s->out.pending_compression = compression; + s->out_comp = NULL; + + bpp_logevent("Will enable %s compression after user authentication", + s->out.pending_compression->text_name); + } else { + s->out.pending_compression = NULL; + + /* 'compression' is always non-NULL, because no compression is + * indicated by ssh_comp_none. But this setup call may return a + * null out_comp. */ + s->out_comp = ssh_compressor_new(compression); + + if (s->out_comp) + bpp_logevent("Initialised %s compression", + ssh_compressor_alg(s->out_comp)->text_name); + } +} + +void ssh2_bpp_new_incoming_crypto( + BinaryPacketProtocol *bpp, + const ssh_cipheralg *cipher, const void *ckey, const void *iv, + const ssh2_macalg *mac, bool etm_mode, const void *mac_key, + const ssh_compression_alg *compression, bool delayed_compression) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + ssh2_bpp_free_incoming_crypto(s); + + if (cipher) { + s->in.cipher = ssh_cipher_new(cipher); + ssh_cipher_setkey(s->in.cipher, ckey); + ssh_cipher_setiv(s->in.cipher, iv); + + bpp_logevent("Initialised %s inbound encryption", + ssh_cipher_alg(s->in.cipher)->text_name); + } else { + s->in.cipher = NULL; + } + s->in.etm_mode = etm_mode; + if (mac) { + s->in.mac = ssh2_mac_new(mac, s->in.cipher); + /* MAC setkey has to follow cipher, just as in outgoing_crypto above */ + ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); + + bpp_logevent("Initialised %s inbound MAC algorithm%s%s", + ssh2_mac_text_name(s->in.mac), + etm_mode ? " (in ETM mode)" : "", + (s->in.cipher && + ssh_cipher_alg(s->in.cipher)->required_mac ? + " (required by cipher)" : "")); + } else { + s->in.mac = NULL; + } + + if (delayed_compression && !s->seen_userauth_success) { + s->in.pending_compression = compression; + s->in_decomp = NULL; + + bpp_logevent("Will enable %s decompression after user authentication", + s->in.pending_compression->text_name); + } else { + s->in.pending_compression = NULL; + + /* 'compression' is always non-NULL, because no compression is + * indicated by ssh_comp_none. But this setup call may return a + * null in_decomp. */ + s->in_decomp = ssh_decompressor_new(compression); + + if (s->in_decomp) + bpp_logevent("Initialised %s decompression", + ssh_decompressor_alg(s->in_decomp)->text_name); + } + + /* Clear the pending_newkeys flag, so that handle_input below will + * start consuming the input data again. */ + s->pending_newkeys = false; + + /* And schedule a run of handle_input, in case there's already + * input data in the queue. */ + queue_idempotent_callback(&s->bpp.ic_in_raw); +} + +bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s; + assert(bpp->vt == &ssh2_bpp_vtable); + s = container_of(bpp, struct ssh2_bpp_state, bpp); + + return s->pending_compression; +} + +static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s) +{ + BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ + + if (s->in.pending_compression) { + s->in_decomp = ssh_decompressor_new(s->in.pending_compression); + bpp_logevent("Initialised delayed %s decompression", + ssh_decompressor_alg(s->in_decomp)->text_name); + s->in.pending_compression = NULL; + } + if (s->out.pending_compression) { + s->out_comp = ssh_compressor_new(s->out.pending_compression); + bpp_logevent("Initialised delayed %s compression", + ssh_compressor_alg(s->out_comp)->text_name); + s->out.pending_compression = NULL; + } +} + +#define BPP_READ(ptr, len) do \ + { \ + bool success; \ + crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ + s->bpp.in_raw, ptr, len)) || \ + s->bpp.input_eof); \ + if (!success) \ + goto eof; \ + ssh_check_frozen(s->bpp.ssh); \ + } while (0) + +#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20) + +static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + + crBegin(s->crState); + + while (1) { + s->maxlen = 0; + s->length = 0; + if (s->in.cipher) + s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize; + else + s->cipherblk = 8; + if (s->cipherblk < 8) + s->cipherblk = 8; + s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0; + + if (s->in.cipher && + (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) && + s->in.mac && !s->in.etm_mode) { + /* + * When dealing with a CBC-mode cipher, we want to avoid the + * possibility of an attacker's tweaking the ciphertext stream + * so as to cause us to feed the same block to the block + * cipher more than once and thus leak information + * (VU#958563). The way we do this is not to take any + * decisions on the basis of anything we've decrypted until + * we've verified it with a MAC. That includes the packet + * length, so we just read data and check the MAC repeatedly, + * and when the MAC passes, see if the length we've got is + * plausible. + * + * This defence is unnecessary in OpenSSH ETM mode, because + * the whole point of ETM mode is that the attacker can't + * tweak the ciphertext stream at all without the MAC + * detecting it before we decrypt anything. + */ + + /* + * Make sure we have buffer space for a maximum-size packet. + */ + unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen; + if (s->bufsize < buflimit) { + s->bufsize = buflimit; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* Read an amount corresponding to the MAC. */ + BPP_READ(s->buf, s->maclen); + + s->packetlen = 0; + ssh2_mac_start(s->in.mac); + put_uint32(s->in.mac, s->in.sequence); + + for (;;) { /* Once around this loop per cipher block. */ + /* Read another cipher-block's worth, and tack it on to + * the end. */ + BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk); + /* Decrypt one more block (a little further back in + * the stream). */ + ssh_cipher_decrypt(s->in.cipher, + s->buf + s->packetlen, s->cipherblk); + + /* Feed that block to the MAC. */ + put_data(s->in.mac, + s->buf + s->packetlen, s->cipherblk); + s->packetlen += s->cipherblk; + + /* See if that gives us a valid packet. */ + if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) && + ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) == + s->packetlen-4)) + break; + if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { + ssh_sw_abort(s->bpp.ssh, + "No valid incoming packet found"); + crStopV; + } + } + s->maxlen = s->packetlen + s->maclen; + + /* + * Now transfer the data into an output packet. + */ + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->maxlen); + } else if (s->in.mac && s->in.etm_mode) { + if (s->bufsize < 4) { + s->bufsize = 4; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * OpenSSH encrypt-then-MAC mode: the packet length is + * unencrypted, unless the cipher supports length encryption. + */ + BPP_READ(s->buf, 4); + + /* Cipher supports length decryption, so do it */ + if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags & + SSH_CIPHER_SEPARATE_LENGTH)) { + /* Keep the packet the same though, so the MAC passes */ + unsigned char len[4]; + memcpy(len, s->buf, 4); + ssh_cipher_decrypt_length( + s->in.cipher, len, 4, s->in.sequence); + s->len = toint(GET_32BIT_MSB_FIRST(len)); + } else { + s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); + } + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + s->len % s->cipherblk != 0) { + ssh_sw_abort(s->bpp.ssh, + "Incoming packet length field was garbled"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, 4); + + /* + * Read the remainder of the packet. + */ + BPP_READ(s->data + 4, s->packetlen + s->maclen - 4); + + /* + * Check the MAC. + */ + if (s->in.mac && !ssh2_mac_verify( + s->in.mac, s->data, s->len + 4, s->in.sequence)) { + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); + crStopV; + } + + /* Decrypt everything between the length field and the MAC. */ + if (s->in.cipher) + ssh_cipher_decrypt( + s->in.cipher, s->data + 4, s->packetlen - 4); + } else { + if (s->bufsize < s->cipherblk) { + s->bufsize = s->cipherblk; + s->buf = sresize(s->buf, s->bufsize, unsigned char); + } + + /* + * Acquire and decrypt the first block of the packet. This will + * contain the length and padding details. + */ + BPP_READ(s->buf, s->cipherblk); + + if (s->in.cipher) + ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk); + + /* + * Now get the length figure. + */ + s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); + + /* + * _Completely_ silly lengths should be stomped on before they + * do us any more damage. + */ + if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || + (s->len + 4) % s->cipherblk != 0) { + ssh_sw_abort(s->bpp.ssh, + "Incoming packet was garbled on decryption"); + crStopV; + } + + /* + * So now we can work out the total packet length. + */ + s->packetlen = s->len + 4; + + /* + * Allocate the packet to return, now we know its length. + */ + s->maxlen = s->packetlen + s->maclen; + s->pktin = snew_plus(PktIn, s->maxlen); + s->pktin->qnode.prev = s->pktin->qnode.next = NULL; + s->pktin->type = 0; + s->pktin->qnode.on_free_queue = false; + s->data = snew_plus_get_aux(s->pktin); + memcpy(s->data, s->buf, s->cipherblk); + + /* + * Read and decrypt the remainder of the packet. + */ + BPP_READ(s->data + s->cipherblk, + s->packetlen + s->maclen - s->cipherblk); + + /* Decrypt everything _except_ the MAC. */ + if (s->in.cipher) + ssh_cipher_decrypt( + s->in.cipher, + s->data + s->cipherblk, s->packetlen - s->cipherblk); + + /* + * Check the MAC. + */ + if (s->in.mac && !ssh2_mac_verify( + s->in.mac, s->data, s->len + 4, s->in.sequence)) { + ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); + crStopV; + } + } + /* Get and sanity-check the amount of random padding. */ + s->pad = s->data[4]; + if (s->pad < 4 || s->len - s->pad < 1) { + ssh_sw_abort(s->bpp.ssh, + "Invalid padding length on received packet"); + crStopV; + } + /* + * This enables us to deduce the payload length. + */ + s->payload = s->len - s->pad - 1; + + s->length = s->payload + 5; + + dts_consume(&s->stats->in, s->packetlen); + + s->pktin->sequence = s->in.sequence++; + if (s->in.cipher) + ssh_cipher_next_message(s->in.cipher); + if (s->in.mac) + ssh2_mac_next_message(s->in.mac); + + s->length = s->packetlen - s->pad; + assert(s->length >= 0); + + /* + * Decompress packet payload. + */ + { + unsigned char *newpayload; + int newlen; + if (s->in_decomp && ssh_decompressor_decompress( + s->in_decomp, s->data + 5, s->length - 5, + &newpayload, &newlen)) { + if (s->maxlen < newlen + 5) { + PktIn *old_pktin = s->pktin; + + s->maxlen = newlen + 5; + s->pktin = snew_plus(PktIn, s->maxlen); + *s->pktin = *old_pktin; /* structure copy */ + s->data = snew_plus_get_aux(s->pktin); + + smemclr(old_pktin, s->packetlen + s->maclen); + sfree(old_pktin); + } + s->length = 5 + newlen; + memcpy(s->data + 5, newpayload, newlen); + sfree(newpayload); + } + } + + /* + * Now we can identify the semantic content of the packet, + * and also the initial type byte. + */ + if (s->length <= 5) { /* == 5 we hope, but robustness */ + /* + * RFC 4253 doesn't explicitly say that completely empty + * packets with no type byte are forbidden. We handle them + * here by giving them a type code larger than 0xFF, which + * will be picked up at the next layer and trigger + * SSH_MSG_UNIMPLEMENTED. + */ + s->pktin->type = SSH_MSG_NO_TYPE_CODE; + s->data += 5; + s->length = 0; + } else { + s->pktin->type = s->data[5]; + s->data += 6; + s->length -= 6; + } + BinarySource_INIT(s->pktin, s->data, s->length); + + if (s->bpp.logctx) { + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, s->pktin->type, false, + make_ptrlen(s->data, s->length), blanks); + log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + s->pktin->type), + s->data, s->length, nblanks, blanks, + &s->pktin->sequence, 0, NULL); + } + + if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { + sfree(s->pktin); + s->pktin = NULL; + continue; + } + + s->pktin->qnode.formal_size = get_avail(s->pktin); + pq_push(&s->bpp.in_pq, s->pktin); + + { + int type = s->pktin->type; + int prev_type = s->prev_type; + s->prev_type = type; + s->pktin = NULL; + + if (s->enforce_next_packet_is_userauth_success) { + /* See EXT_INFO handler below */ + if (type != SSH2_MSG_USERAUTH_SUCCESS) { + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "not either preceded by NEWKEYS or " + "followed by USERAUTH_SUCCESS"); + return; + } + s->enforce_next_packet_is_userauth_success = false; + } + + if (type == SSH2_MSG_NEWKEYS) { + if (s->nnewkeys < 2) + s->nnewkeys++; + /* + * Mild layer violation: in this situation we must + * suspend processing of the input byte stream until + * the transport layer has initialised the new keys by + * calling ssh2_bpp_new_incoming_crypto above. + */ + s->pending_newkeys = true; + crWaitUntilV(!s->pending_newkeys); + continue; + } + + if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) { + /* + * Another one: if we were configured with OpenSSH's + * deferred compression which is triggered on receipt + * of USERAUTH_SUCCESS, then this is the moment to + * turn on compression. + */ + ssh2_bpp_enable_pending_compression(s); + + /* + * Whether or not we were doing delayed compression in + * _this_ set of crypto parameters, we should set a + * flag indicating that we're now authenticated, so + * that a delayed compression method enabled in any + * future rekey will be treated as un-delayed. + */ + s->seen_userauth_success = true; + } + + if (type == SSH2_MSG_EXT_INFO) { + /* + * And another: enforce that an incoming EXT_INFO is + * either the message immediately after the initial + * NEWKEYS, or (if we're the client) the one + * immediately before USERAUTH_SUCCESS. + */ + if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) { + /* OK - this is right after the first NEWKEYS. */ + } else if (s->is_server) { + /* We're the server, so they're the client. + * Clients may not send EXT_INFO at _any_ other + * time. */ + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "that was not immediately after the " + "initial NEWKEYS"); + return; + } else if (s->nnewkeys > 0 && s->seen_userauth_success) { + /* We're the client, so they're the server. In + * that case they may also send EXT_INFO + * immediately before USERAUTH_SUCCESS. Error out + * immediately if this can't _possibly_ be that + * moment (because we haven't even seen NEWKEYS + * yet, or because we've already seen + * USERAUTH_SUCCESS). */ + ssh_proto_error(s->bpp.ssh, + "Remote side sent SSH2_MSG_EXT_INFO " + "after USERAUTH_SUCCESS"); + return; + } else { + /* This _could_ be OK, provided the next packet is + * USERAUTH_SUCCESS. Set a flag to remember to + * fault it if not. */ + s->enforce_next_packet_is_userauth_success = true; + } + } + + if (s->pending_compression && userauth_range(type)) { + /* + * Receiving any userauth message at all indicates + * that we're not about to turn on delayed compression + * - either because we just _have_ done, or because + * this message is a USERAUTH_FAILURE or some kind of + * intermediate 'please send more data' continuation + * message. Either way, we turn off the outgoing + * packet blockage for now, and release any queued + * output packets, so that we can make another attempt + * to authenticate. The next userauth packet we send + * will re-block the output direction. + */ + s->pending_compression = false; + queue_idempotent_callback(&s->bpp.ic_out_pq); + } + } + } + + eof: + /* + * We've seen EOF. But we might have pushed stuff on the outgoing + * packet queue first, and that stuff _might_ include a DISCONNECT + * message, in which case we'd like to use that as the diagnostic. + * So first wait for the queue to have been processed. + */ + crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq)); + if (!s->bpp.expect_close) { + ssh_remote_error(s->bpp.ssh, + "Remote side unexpectedly closed network connection"); + } else { + ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); + } + return; /* avoid touching s now it's been freed */ + + crFinishV; +} + +static PktOut *ssh2_bpp_new_pktout(int pkt_type) +{ + PktOut *pkt = ssh_new_packet(); + pkt->length = 5; /* space for packet length + padding length */ + pkt->minlen = 0; + pkt->type = pkt_type; + put_byte(pkt, pkt_type); + pkt->prefix = pkt->length; + return pkt; +} + +static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) +{ + int origlen, cipherblk, maclen, padding, unencrypted_prefix, i; + + if (s->bpp.logctx) { + ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, + pkt->length - pkt->prefix); + logblank_t blanks[MAX_BLANKS]; + int nblanks = ssh2_censor_packet( + s->bpp.pls, pkt->type, true, pktdata, blanks); + log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, + ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, + pkt->type), + pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence, + pkt->downstream_id, pkt->additional_log_text); + } + + cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8; + cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ + + if (s->out_comp) { + unsigned char *newpayload; + int minlen, newlen; + + /* + * Compress packet payload. + */ + minlen = pkt->minlen; + if (minlen) { + /* + * Work out how much compressed data we need (at least) to + * make the overall packet length come to pkt->minlen. + */ + if (s->out.mac) + minlen -= ssh2_mac_alg(s->out.mac)->len; + minlen -= 8; /* length field + min padding */ + } + + ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5, + &newpayload, &newlen, minlen); + pkt->length = 5; + put_data(pkt, newpayload, newlen); + sfree(newpayload); + } + + /* + * Add padding. At least four bytes, and must also bring total + * length (minus MAC) up to a multiple of the block size. + * If pkt->forcepad is set, make sure the packet is at least that size + * after padding. + */ + padding = 4; + unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0; + padding += + (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) + % cipherblk; + assert(padding <= 255); + maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0; + origlen = pkt->length; + for (i = 0; i < padding; i++) + put_byte(pkt, 0); /* make space for random padding */ + random_read(pkt->data + origlen, padding); + pkt->data[4] = padding; + PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4); + + /* Encrypt length if the scheme requires it */ + if (s->out.cipher && + (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) { + ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4, + s->out.sequence); + } + + put_padding(pkt, maclen, 0); + + if (s->out.mac && s->out.etm_mode) { + /* + * OpenSSH-defined encrypt-then-MAC protocol. + */ + if (s->out.cipher) + ssh_cipher_encrypt(s->out.cipher, + pkt->data + 4, origlen + padding - 4); + ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, + s->out.sequence); + } else { + /* + * SSH-2 standard protocol. + */ + if (s->out.mac) + ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, + s->out.sequence); + if (s->out.cipher) + ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding); + } + + s->out.sequence++; /* whether or not we MACed */ + if (s->out.cipher) + ssh_cipher_next_message(s->out.cipher); + if (s->out.mac) + ssh2_mac_next_message(s->out.mac); + + dts_consume(&s->stats->out, origlen + padding); +} + +static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt) +{ + if (pkt->minlen > 0 && !s->out_comp) { + /* + * If we've been told to pad the packet out to a given minimum + * length, but we're not compressing (and hence can't get the + * compression to do the padding by pointlessly opening and + * closing zlib blocks), then our other strategy is to precede + * this message with an SSH_MSG_IGNORE that makes it up to the + * right length. + * + * A third option in principle, and the most obviously + * sensible, would be to set the explicit padding field in the + * packet to more than its minimum value. Sadly, that turns + * out to break some servers (our institutional memory thinks + * Cisco in particular) and so we abandoned that idea shortly + * after trying it. + */ + + /* + * Calculate the length we expect the real packet to have. + */ + int block, length; + PktOut *ignore_pkt; + + block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0; + if (block < 8) + block = 8; + length = pkt->length; + length += 4; /* minimum 4 byte padding */ + length += block-1; + length -= (length % block); + if (s->out.mac) + length += ssh2_mac_alg(s->out.mac)->len; + + if (length < pkt->minlen) { + /* + * We need an ignore message. Calculate its length. + */ + length = pkt->minlen - length; + + /* + * And work backwards from that to the length of the + * contained string. + */ + if (s->out.mac) + length -= ssh2_mac_alg(s->out.mac)->len; + length -= 8; /* length field + min padding */ + length -= 5; /* type code + string length prefix */ + + if (length < 0) + length = 0; + + ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE); + put_uint32(ignore_pkt, length); + size_t origlen = ignore_pkt->length; + for (size_t i = 0; i < length; i++) + put_byte(ignore_pkt, 0); /* make space for random padding */ + random_read(ignore_pkt->data + origlen, length); + ssh2_bpp_format_packet_inner(s, ignore_pkt); + bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length); + ssh_free_pktout(ignore_pkt); + } + } + + ssh2_bpp_format_packet_inner(s, pkt); + bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); +} + +static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) +{ + struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); + PktOut *pkt; + int n_userauth; + + /* + * Count the userauth packets in the queue. + */ + n_userauth = 0; + for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL; + pkt = pq_next(&s->bpp.out_pq, pkt)) + if (userauth_range(pkt->type)) + n_userauth++; + + if (s->pending_compression && !n_userauth) { + /* + * We're currently blocked from sending any outgoing packets + * until the other end tells us whether we're going to have to + * enable compression or not. + * + * If our end has pushed a userauth packet on the queue, that + * must mean it knows that a USERAUTH_SUCCESS is not + * immediately forthcoming, so we unblock ourselves and send + * up to and including that packet. But in this if statement, + * there aren't any, so we're still blocked. + */ + return; + } + + if (s->cbc_ignore_workaround) { + /* + * 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 inserting an + * SSH_MSG_IGNORE if the last cipher block of the previous + * packet has already been sent to the network (which we + * approximate conservatively by checking if it's vanished + * from out_raw). + */ + if (bufchain_size(s->bpp.out_raw) < + (ssh_cipher_alg(s->out.cipher)->blksize + + ssh2_mac_alg(s->out.mac)->len)) { + /* + * There's less data in out_raw than the MAC size plus the + * cipher block size, which means at least one byte of + * that cipher block must already have left. Add an + * IGNORE. + */ + pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE); + put_stringz(pkt, ""); + ssh2_bpp_format_packet(s, pkt); + } + } + + while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { + int type = pkt->type; + + if (userauth_range(type)) + n_userauth--; + + ssh2_bpp_format_packet(s, pkt); + ssh_free_pktout(pkt); + + if (n_userauth == 0 && s->out.pending_compression && !s->is_server) { + /* + * This is the last userauth packet in the queue, so + * unless our side decides to send another one in future, + * we have to assume will potentially provoke + * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets + * until we see the reply. + */ + s->pending_compression = true; + return; + } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) { + ssh2_bpp_enable_pending_compression(s); + } + } + + ssh_sendbuffer_changed(bpp->ssh); +} |