Mercurial > dropbear
view packet.c @ 1760:2406a9987810
Add first try at fuzzing custom mutator
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sun, 25 Oct 2020 22:52:36 +0800 |
parents | 3b9b427925a0 |
children |
line wrap: on
line source
/* * Dropbear - a SSH2 server * * Copyright (c) 2002,2003 Matt Johnston * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "includes.h" #include "packet.h" #include "session.h" #include "dbutil.h" #include "ssh.h" #include "algo.h" #include "buffer.h" #include "kex.h" #include "dbrandom.h" #include "service.h" #include "auth.h" #include "channel.h" #include "netio.h" #include "runopts.h" static int read_packet_init(void); static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, buffer * clear_buf, unsigned int clear_len, unsigned char *output_mac); static int checkmac(void); /* For exact details see http://www.zlib.net/zlib_tech.html * 5 bytes per 16kB block, plus 6 bytes for the stream. * We might allocate 5 unnecessary bytes here if it's an * exact multiple. */ #define ZLIB_COMPRESS_EXPANSION (((RECV_MAX_PAYLOAD_LEN/16384)+1)*5 + 6) #define ZLIB_DECOMPRESS_INCR 1024 #ifndef DISABLE_ZLIB static buffer* buf_decompress(const buffer* buf, unsigned int len); static void buf_compress(buffer * dest, buffer * src, unsigned int len); #endif /* non-blocking function writing out a current encrypted packet */ void write_packet() { ssize_t written; #if defined(HAVE_WRITEV) && (defined(IOV_MAX) || defined(UIO_MAXIOV)) /* 50 is somewhat arbitrary */ unsigned int iov_count = 50; struct iovec iov[50]; #else int len; buffer* writebuf; #endif TRACE2(("enter write_packet")) dropbear_assert(!isempty(&ses.writequeue)); #if defined(HAVE_WRITEV) && (defined(IOV_MAX) || defined(UIO_MAXIOV)) packet_queue_to_iovec(&ses.writequeue, iov, &iov_count); /* This may return EAGAIN. The main loop sometimes calls write_packet() without bothering to test with select() since it's likely to be necessary */ #if DROPBEAR_FUZZ if (fuzz.fuzzing) { /* pretend to write one packet at a time */ /* TODO(fuzz): randomise amount written based on the fuzz input */ written = iov[0].iov_len; } else #endif { written = writev(ses.sock_out, iov, iov_count); if (written < 0) { if (errno == EINTR || errno == EAGAIN) { TRACE2(("leave write_packet: EINTR")) return; } else { dropbear_exit("Error writing: %s", strerror(errno)); } } } packet_queue_consume(&ses.writequeue, written); ses.writequeue_len -= written; if (written == 0) { ses.remoteclosed(); } #else /* No writev () */ #if DROPBEAR_FUZZ _Static_assert(0, "No fuzzing code for no-writev writes"); #endif /* Get the next buffer in the queue of encrypted packets to write*/ writebuf = (buffer*)examine(&ses.writequeue); len = writebuf->len - writebuf->pos; dropbear_assert(len > 0); /* Try to write as much as possible */ written = write(ses.sock_out, buf_getptr(writebuf, len), len); if (written < 0) { if (errno == EINTR || errno == EAGAIN) { TRACE2(("leave writepacket: EINTR")) return; } else { dropbear_exit("Error writing: %s", strerror(errno)); } } if (written == 0) { ses.remoteclosed(); } ses.writequeue_len -= written; if (written == len) { /* We've finished with the packet, free it */ dequeue(&ses.writequeue); buf_free(writebuf); writebuf = NULL; } else { /* More packet left to write, leave it in the queue for later */ buf_incrpos(writebuf, written); } #endif /* writev */ TRACE2(("leave write_packet")) } /* Non-blocking function reading available portion of a packet into the * ses's buffer, decrypting the length if encrypted, decrypting the * full portion if possible */ void read_packet() { int len; unsigned int maxlen; unsigned char blocksize; TRACE2(("enter read_packet")) blocksize = ses.keys->recv.algo_crypt->blocksize; if (ses.readbuf == NULL || ses.readbuf->len < blocksize) { int ret; /* In the first blocksize of a packet */ /* Read the first blocksize of the packet, so we can decrypt it and * find the length of the whole packet */ ret = read_packet_init(); if (ret == DROPBEAR_FAILURE) { /* didn't read enough to determine the length */ TRACE2(("leave read_packet: packetinit done")) return; } } /* Attempt to read the remainder of the packet, note that there * mightn't be any available (EAGAIN) */ maxlen = ses.readbuf->len - ses.readbuf->pos; if (maxlen == 0) { /* Occurs when the packet is only a single block long and has all * been read in read_packet_init(). Usually means that MAC is disabled */ len = 0; } else { len = read(ses.sock_in, buf_getptr(ses.readbuf, maxlen), maxlen); if (len == 0) { ses.remoteclosed(); } if (len < 0) { if (errno == EINTR || errno == EAGAIN) { TRACE2(("leave read_packet: EINTR or EAGAIN")) return; } else { dropbear_exit("Error reading: %s", strerror(errno)); } } buf_incrpos(ses.readbuf, len); } if ((unsigned int)len == maxlen) { /* The whole packet has been read */ decrypt_packet(); /* The main select() loop process_packet() to * handle the packet contents... */ } TRACE2(("leave read_packet")) } /* Function used to read the initial portion of a packet, and determine the * length. Only called during the first BLOCKSIZE of a packet. */ /* Returns DROPBEAR_SUCCESS if the length is determined, * DROPBEAR_FAILURE otherwise */ static int read_packet_init() { unsigned int maxlen; int slen; unsigned int len, plen; unsigned int blocksize; unsigned int macsize; blocksize = ses.keys->recv.algo_crypt->blocksize; macsize = ses.keys->recv.algo_mac->hashsize; if (ses.readbuf == NULL) { /* start of a new packet */ ses.readbuf = buf_new(INIT_READBUF); } maxlen = blocksize - ses.readbuf->pos; /* read the rest of the packet if possible */ slen = read(ses.sock_in, buf_getwriteptr(ses.readbuf, maxlen), maxlen); if (slen == 0) { ses.remoteclosed(); } if (slen < 0) { if (errno == EINTR || errno == EAGAIN) { TRACE2(("leave read_packet_init: EINTR")) return DROPBEAR_FAILURE; } dropbear_exit("Error reading: %s", strerror(errno)); } buf_incrwritepos(ses.readbuf, slen); if ((unsigned int)slen != maxlen) { /* don't have enough bytes to determine length, get next time */ return DROPBEAR_FAILURE; } /* now we have the first block, need to get packet length, so we decrypt * the first block (only need first 4 bytes) */ buf_setpos(ses.readbuf, 0); #if DROPBEAR_AEAD_MODE if (ses.keys->recv.crypt_mode->aead_crypt) { if (ses.keys->recv.crypt_mode->aead_getlength(ses.recvseq, buf_getptr(ses.readbuf, blocksize), &plen, blocksize, &ses.keys->recv.cipher_state) != CRYPT_OK) { dropbear_exit("Error decrypting"); } len = plen + 4 + macsize; } else #endif { if (ses.keys->recv.crypt_mode->decrypt(buf_getptr(ses.readbuf, blocksize), buf_getwriteptr(ses.readbuf, blocksize), blocksize, &ses.keys->recv.cipher_state) != CRYPT_OK) { dropbear_exit("Error decrypting"); } plen = buf_getint(ses.readbuf) + 4; len = plen + macsize; } TRACE2(("packet size is %u, block %u mac %u", len, blocksize, macsize)) /* check packet length */ if ((len > RECV_MAX_PACKET_LEN) || (plen < blocksize) || (plen % blocksize != 0)) { dropbear_exit("Integrity error (bad packet size %u)", len); } if (len > ses.readbuf->size) { ses.readbuf = buf_resize(ses.readbuf, len); } buf_setlen(ses.readbuf, len); buf_setpos(ses.readbuf, blocksize); return DROPBEAR_SUCCESS; } /* handle the received packet */ void decrypt_packet() { unsigned char blocksize; unsigned char macsize; unsigned int padlen; unsigned int len; TRACE2(("enter decrypt_packet")) blocksize = ses.keys->recv.algo_crypt->blocksize; macsize = ses.keys->recv.algo_mac->hashsize; ses.kexstate.datarecv += ses.readbuf->len; #if DROPBEAR_AEAD_MODE if (ses.keys->recv.crypt_mode->aead_crypt) { /* first blocksize is not decrypted yet */ buf_setpos(ses.readbuf, 0); /* decrypt it in-place */ len = ses.readbuf->len - macsize - ses.readbuf->pos; if (ses.keys->recv.crypt_mode->aead_crypt(ses.recvseq, buf_getptr(ses.readbuf, len + macsize), buf_getwriteptr(ses.readbuf, len), len, macsize, &ses.keys->recv.cipher_state, LTC_DECRYPT) != CRYPT_OK) { dropbear_exit("Error decrypting"); } buf_incrpos(ses.readbuf, len); } else #endif { /* we've already decrypted the first blocksize in read_packet_init */ buf_setpos(ses.readbuf, blocksize); /* decrypt it in-place */ len = ses.readbuf->len - macsize - ses.readbuf->pos; if (ses.keys->recv.crypt_mode->decrypt( buf_getptr(ses.readbuf, len), buf_getwriteptr(ses.readbuf, len), len, &ses.keys->recv.cipher_state) != CRYPT_OK) { dropbear_exit("Error decrypting"); } buf_incrpos(ses.readbuf, len); /* check the hmac */ if (checkmac() != DROPBEAR_SUCCESS) { dropbear_exit("Integrity error"); } } #if DROPBEAR_FUZZ fuzz_dump(ses.readbuf->data, ses.readbuf->len); #endif /* get padding length */ buf_setpos(ses.readbuf, PACKET_PADDING_OFF); padlen = buf_getbyte(ses.readbuf); /* payload length */ /* - 4 - 1 is for LEN and PADLEN values */ len = ses.readbuf->len - padlen - 4 - 1 - macsize; if ((len > RECV_MAX_PAYLOAD_LEN+ZLIB_COMPRESS_EXPANSION) || (len < 1)) { dropbear_exit("Bad packet size %u", len); } buf_setpos(ses.readbuf, PACKET_PAYLOAD_OFF); #ifndef DISABLE_ZLIB if (is_compress_recv()) { /* decompress */ ses.payload = buf_decompress(ses.readbuf, len); buf_setpos(ses.payload, 0); ses.payload_beginning = 0; buf_free(ses.readbuf); } else #endif { ses.payload = ses.readbuf; ses.payload_beginning = ses.payload->pos; buf_setlen(ses.payload, ses.payload->pos + len); } ses.readbuf = NULL; ses.recvseq++; TRACE2(("leave decrypt_packet")) } /* Checks the mac at the end of a decrypted readbuf. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ static int checkmac() { unsigned char mac_bytes[MAX_MAC_LEN]; unsigned int mac_size, contents_len; mac_size = ses.keys->recv.algo_mac->hashsize; contents_len = ses.readbuf->len - mac_size; buf_setpos(ses.readbuf, 0); make_mac(ses.recvseq, &ses.keys->recv, ses.readbuf, contents_len, mac_bytes); #if DROPBEAR_FUZZ if (fuzz.fuzzing) { /* fail 1 in 2000 times to test error path. */ unsigned int value = 0; if (mac_size > sizeof(value)) { memcpy(&value, mac_bytes, sizeof(value)); } if (value % 2000 == 99) { return DROPBEAR_FAILURE; } return DROPBEAR_SUCCESS; } #endif /* compare the hash */ buf_setpos(ses.readbuf, contents_len); if (constant_time_memcmp(mac_bytes, buf_getptr(ses.readbuf, mac_size), mac_size) != 0) { return DROPBEAR_FAILURE; } else { return DROPBEAR_SUCCESS; } } #ifndef DISABLE_ZLIB /* returns a pointer to a newly created buffer */ static buffer* buf_decompress(const buffer* buf, unsigned int len) { int result; buffer * ret; z_streamp zstream; zstream = ses.keys->recv.zstream; ret = buf_new(len); zstream->avail_in = len; zstream->next_in = buf_getptr(buf, len); /* decompress the payload, incrementally resizing the output buffer */ while (1) { zstream->avail_out = ret->size - ret->pos; zstream->next_out = buf_getwriteptr(ret, zstream->avail_out); result = inflate(zstream, Z_SYNC_FLUSH); buf_setlen(ret, ret->size - zstream->avail_out); buf_setpos(ret, ret->len); if (result != Z_BUF_ERROR && result != Z_OK) { dropbear_exit("zlib error"); } if (zstream->avail_in == 0 && (zstream->avail_out != 0 || result == Z_BUF_ERROR)) { /* we can only exit if avail_out hasn't all been used, * and there's no remaining input */ return ret; } if (zstream->avail_out == 0) { int new_size = 0; if (ret->size >= RECV_MAX_PAYLOAD_LEN) { /* Already been increased as large as it can go, * yet didn't finish up the decompression */ dropbear_exit("bad packet, oversized decompressed"); } new_size = MIN(RECV_MAX_PAYLOAD_LEN, ret->size + ZLIB_DECOMPRESS_INCR); ret = buf_resize(ret, new_size); } } } #endif /* returns 1 if the packet is a valid type during kex (see 7.1 of rfc4253) */ static int packet_is_okay_kex(unsigned char type) { if (type >= SSH_MSG_USERAUTH_REQUEST) { return 0; } if (type == SSH_MSG_SERVICE_REQUEST || type == SSH_MSG_SERVICE_ACCEPT) { return 0; } if (type == SSH_MSG_KEXINIT) { /* XXX should this die horribly if !dataallowed ?? */ return 0; } return 1; } static void enqueue_reply_packet() { struct packetlist * new_item = NULL; new_item = m_malloc(sizeof(struct packetlist)); new_item->next = NULL; new_item->payload = buf_newcopy(ses.writepayload); buf_setpos(ses.writepayload, 0); buf_setlen(ses.writepayload, 0); if (ses.reply_queue_tail) { ses.reply_queue_tail->next = new_item; } else { ses.reply_queue_head = new_item; } ses.reply_queue_tail = new_item; } void maybe_flush_reply_queue() { struct packetlist *tmp_item = NULL, *curr_item = NULL; if (!ses.dataallowed) { TRACE(("maybe_empty_reply_queue - no data allowed")) return; } for (curr_item = ses.reply_queue_head; curr_item; ) { CHECKCLEARTOWRITE(); buf_putbytes(ses.writepayload, curr_item->payload->data, curr_item->payload->len); buf_free(curr_item->payload); tmp_item = curr_item; curr_item = curr_item->next; m_free(tmp_item); encrypt_packet(); } ses.reply_queue_head = ses.reply_queue_tail = NULL; } /* encrypt the writepayload, putting into writebuf, ready for write_packet() * to put on the wire */ void encrypt_packet() { unsigned char padlen; unsigned char blocksize, mac_size; buffer * writebuf; /* the packet which will go on the wire. This is encrypted in-place. */ unsigned char packet_type; unsigned int len, encrypt_buf_size; unsigned char mac_bytes[MAX_MAC_LEN]; time_t now; TRACE2(("enter encrypt_packet()")) buf_setpos(ses.writepayload, 0); packet_type = buf_getbyte(ses.writepayload); buf_setpos(ses.writepayload, 0); TRACE2(("encrypt_packet type is %d", packet_type)) if ((!ses.dataallowed && !packet_is_okay_kex(packet_type))) { /* During key exchange only particular packets are allowed. Since this packet_type isn't OK we just enqueue it to send after the KEX, see maybe_flush_reply_queue */ enqueue_reply_packet(); return; } blocksize = ses.keys->trans.algo_crypt->blocksize; mac_size = ses.keys->trans.algo_mac->hashsize; /* Encrypted packet len is payload+5. We need to then make sure * there is enough space for padding or MIN_PACKET_LEN. * Add extra 3 since we need at least 4 bytes of padding */ encrypt_buf_size = (ses.writepayload->len+4+1) + MAX(MIN_PACKET_LEN, blocksize) + 3 /* add space for the MAC at the end */ + mac_size #ifndef DISABLE_ZLIB /* some extra in case 'compression' makes it larger */ + ZLIB_COMPRESS_EXPANSION #endif /* and an extra cleartext (stripped before transmission) byte for the * packet type */ + 1; writebuf = buf_new(encrypt_buf_size); buf_setlen(writebuf, PACKET_PAYLOAD_OFF); buf_setpos(writebuf, PACKET_PAYLOAD_OFF); #ifndef DISABLE_ZLIB /* compression */ if (is_compress_trans()) { buf_compress(writebuf, ses.writepayload, ses.writepayload->len); } else #endif { memcpy(buf_getwriteptr(writebuf, ses.writepayload->len), buf_getptr(ses.writepayload, ses.writepayload->len), ses.writepayload->len); buf_incrwritepos(writebuf, ses.writepayload->len); } /* finished with payload */ buf_setpos(ses.writepayload, 0); buf_setlen(ses.writepayload, 0); /* length of padding - packet length excluding the packetlength uint32 * field in aead mode must be a multiple of blocksize, with a minimum of * 4 bytes of padding */ len = writebuf->len; #if DROPBEAR_AEAD_MODE if (ses.keys->trans.crypt_mode->aead_crypt) { len -= 4; } #endif padlen = blocksize - len % blocksize; if (padlen < 4) { padlen += blocksize; } /* check for min packet length */ if (writebuf->len + padlen < MIN_PACKET_LEN) { padlen += blocksize; } buf_setpos(writebuf, 0); /* packet length excluding the packetlength uint32 */ buf_putint(writebuf, writebuf->len + padlen - 4); /* padding len */ buf_putbyte(writebuf, padlen); /* actual padding */ buf_setpos(writebuf, writebuf->len); buf_incrlen(writebuf, padlen); genrandom(buf_getptr(writebuf, padlen), padlen); #if DROPBEAR_AEAD_MODE if (ses.keys->trans.crypt_mode->aead_crypt) { /* do the actual encryption, in-place */ buf_setpos(writebuf, 0); /* encrypt it in-place*/ len = writebuf->len; buf_incrlen(writebuf, mac_size); if (ses.keys->trans.crypt_mode->aead_crypt(ses.transseq, buf_getptr(writebuf, len), buf_getwriteptr(writebuf, len + mac_size), len, mac_size, &ses.keys->trans.cipher_state, LTC_ENCRYPT) != CRYPT_OK) { dropbear_exit("Error encrypting"); } buf_incrpos(writebuf, len + mac_size); } else #endif { make_mac(ses.transseq, &ses.keys->trans, writebuf, writebuf->len, mac_bytes); /* do the actual encryption, in-place */ buf_setpos(writebuf, 0); /* encrypt it in-place*/ len = writebuf->len; if (ses.keys->trans.crypt_mode->encrypt( buf_getptr(writebuf, len), buf_getwriteptr(writebuf, len), len, &ses.keys->trans.cipher_state) != CRYPT_OK) { dropbear_exit("Error encrypting"); } buf_incrpos(writebuf, len); /* stick the MAC on it */ buf_putbytes(writebuf, mac_bytes, mac_size); } /* Update counts */ ses.kexstate.datatrans += writebuf->len; writebuf_enqueue(writebuf); /* Update counts */ ses.transseq++; now = monotonic_now(); ses.last_packet_time_any_sent = now; /* idle timeout shouldn't be affected by responses to keepalives. send_msg_keepalive() itself also does tricks with ses.last_packet_idle_time - read that if modifying this code */ if (packet_type != SSH_MSG_REQUEST_FAILURE && packet_type != SSH_MSG_UNIMPLEMENTED && packet_type != SSH_MSG_IGNORE) { ses.last_packet_time_idle = now; } TRACE2(("leave encrypt_packet()")) } void writebuf_enqueue(buffer * writebuf) { /* enqueue the packet for sending. It will get freed after transmission. */ buf_setpos(writebuf, 0); enqueue(&ses.writequeue, (void*)writebuf); ses.writequeue_len += writebuf->len; } /* Create the packet mac, and append H(seqno|clearbuf) to the output */ /* output_mac must have ses.keys->trans.algo_mac->hashsize bytes. */ static void make_mac(unsigned int seqno, const struct key_context_directional * key_state, buffer * clear_buf, unsigned int clear_len, unsigned char *output_mac) { unsigned char seqbuf[4]; unsigned long bufsize; hmac_state hmac; if (key_state->algo_mac->hashsize > 0) { /* calculate the mac */ if (hmac_init(&hmac, key_state->hash_index, key_state->mackey, key_state->algo_mac->keysize) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* sequence number */ STORE32H(seqno, seqbuf); if (hmac_process(&hmac, seqbuf, 4) != CRYPT_OK) { dropbear_exit("HMAC error"); } /* the actual contents */ buf_setpos(clear_buf, 0); if (hmac_process(&hmac, buf_getptr(clear_buf, clear_len), clear_len) != CRYPT_OK) { dropbear_exit("HMAC error"); } bufsize = MAX_MAC_LEN; if (hmac_done(&hmac, output_mac, &bufsize) != CRYPT_OK) { dropbear_exit("HMAC error"); } } TRACE2(("leave writemac")) } #ifndef DISABLE_ZLIB /* compresses len bytes from src, outputting to dest (starting from the * respective current positions. dest must have sufficient space, * len+ZLIB_COMPRESS_EXPANSION */ static void buf_compress(buffer * dest, buffer * src, unsigned int len) { unsigned int endpos = src->pos + len; int result; TRACE2(("enter buf_compress")) dropbear_assert(dest->size - dest->pos >= len+ZLIB_COMPRESS_EXPANSION); ses.keys->trans.zstream->avail_in = endpos - src->pos; ses.keys->trans.zstream->next_in = buf_getptr(src, ses.keys->trans.zstream->avail_in); ses.keys->trans.zstream->avail_out = dest->size - dest->pos; ses.keys->trans.zstream->next_out = buf_getwriteptr(dest, ses.keys->trans.zstream->avail_out); result = deflate(ses.keys->trans.zstream, Z_SYNC_FLUSH); buf_setpos(src, endpos - ses.keys->trans.zstream->avail_in); buf_setlen(dest, dest->size - ses.keys->trans.zstream->avail_out); buf_setpos(dest, dest->len); if (result != Z_OK) { dropbear_exit("zlib error"); } /* fails if destination buffer wasn't large enough */ dropbear_assert(ses.keys->trans.zstream->avail_in == 0); TRACE2(("leave buf_compress")) } #endif