diff options
Diffstat (limited to 'lib/gnutls_buffers.c')
-rw-r--r-- | lib/gnutls_buffers.c | 436 |
1 files changed, 347 insertions, 89 deletions
diff --git a/lib/gnutls_buffers.c b/lib/gnutls_buffers.c index 5c88319663..c8ad1dc270 100644 --- a/lib/gnutls_buffers.c +++ b/lib/gnutls_buffers.c @@ -168,9 +168,9 @@ _gnutls_dgram_read (gnutls_session_t session, mbuffer_st **bufel, gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; if (recv_size > max_size) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + recv_size = max_size; - *bufel = _mbuffer_alloc (0, _gnutls_get_max_decrypted_data(session)); + *bufel = _mbuffer_alloc (0, max_size); if (*bufel == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); @@ -179,7 +179,6 @@ _gnutls_dgram_read (gnutls_session_t session, mbuffer_st **bufel, session->internals.direction = 0; reset_errno (session); - i = pull_func (fd, ptr, recv_size); if (i < 0) @@ -214,7 +213,7 @@ _gnutls_dgram_read (gnutls_session_t session, mbuffer_st **bufel, /* If we get here, we likely have a stream socket. * FIXME: this probably breaks DCCP. */ gnutls_assert (); - ret = GNUTLS_E_INTERNAL_ERROR; + ret = 0; goto cleanup; } @@ -478,14 +477,6 @@ _gnutls_io_read_buffered (gnutls_session_t session, size_t total, } } - if(_gnutls_is_dtls(session) - && session->internals.record_recv_buffer.byte_length != 0) - { - /* Attempt to read across records while using DTLS. */ - gnutls_assert(); - return GNUTLS_E_INVALID_REQUEST; - } - /* min is over zero. recvdata is the data we must * receive in order to return the requested data. */ @@ -736,6 +727,9 @@ _gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, u else return GNUTLS_E_TIMEDOUT; } +/* HANDSHAKE buffers part + */ + /* This function writes the data that are left in the * Handshake write buffer (ie. because the previous write was * interrupted. @@ -831,109 +825,385 @@ _gnutls_handshake_io_cache_int (gnutls_session_t session, return 0; } -/* Skips a handshake packet +static int handshake_compare(const void* _e1, const void* _e2) +{ +const handshake_buffer_st* e1 = _e1; +const handshake_buffer_st* e2 = _e2; + + if (e1->sequence <= e2->sequence) + return 1; + else + return -1; +} + +#define SSL2_HEADERS 1 +static int +parse_handshake_header (gnutls_session_t session, mbuffer_st* bufel, gnutls_handshake_description_t htype, + handshake_buffer_st* hsk) +{ + uint8_t *dataptr = NULL; /* for realloc */ + size_t handshake_header_size = HANDSHAKE_HEADER_SIZE(session), data_size; + + /* Note: SSL2_HEADERS == 1 */ + if (_mbuffer_get_udata_size(bufel) < handshake_header_size) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + dataptr = _mbuffer_get_udata_ptr(bufel); + + /* if reading a client hello of SSLv2 */ + if (!IS_DTLS(session) && htype == GNUTLS_HANDSHAKE_CLIENT_HELLO && + bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2) + { + hsk->length = _mbuffer_get_udata_size(bufel) - SSL2_HEADERS; /* we've read the first byte */ + + handshake_header_size = SSL2_HEADERS; /* we've already read one byte */ + + if (dataptr[0] != GNUTLS_HANDSHAKE_CLIENT_HELLO) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + hsk->htype = GNUTLS_HANDSHAKE_CLIENT_HELLO_V2; + + hsk->sequence = 0; + hsk->start_offset = 0; + hsk->end_offset = hsk->length; + } + else /* TLS handshake headers */ + { + + hsk->htype = dataptr[0]; + + /* we do not use DECR_LEN because we know + * that the packet has enough data. + */ + hsk->length = _gnutls_read_uint24 (&dataptr[1]); + handshake_header_size = HANDSHAKE_HEADER_SIZE(session); + + if (IS_DTLS(session)) + { + hsk->sequence = _gnutls_read_uint16 (&dataptr[4]); + hsk->start_offset = _gnutls_read_uint24 (&dataptr[6]); + hsk->end_offset = hsk->start_offset + _gnutls_read_uint24 (&dataptr[9]); + } + else + { + hsk->sequence = 0; + hsk->start_offset = 0; + hsk->end_offset = _mbuffer_get_udata_size(bufel) - handshake_header_size; + } + } + + /* make the length offset */ + if (hsk->end_offset > 0) hsk->end_offset--; + + _gnutls_handshake_log ("HSK[%p]: %s was received. Length %d, frag offset %d, frag length: %d, sequence: %d\n", + session, _gnutls_handshake2str (hsk->htype), + (int) hsk->length, hsk->start_offset, hsk->end_offset-hsk->start_offset+1, (int)hsk->sequence); + + hsk->header_size = handshake_header_size; + memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel), handshake_header_size); + + data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; + + if (hsk->length > 0 && (hsk->start_offset >= hsk->end_offset || + hsk->end_offset-hsk->start_offset >= data_size || + hsk->end_offset >= hsk->length)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + else if (hsk->length == 0 && hsk->end_offset != 0 && hsk->start_offset != 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + return handshake_header_size; +} + +static void _gnutls_handshake_buffer_move(handshake_buffer_st* dst, handshake_buffer_st* src) +{ + memcpy(dst, src, sizeof(*dst)); + memset(src, 0, sizeof(*src)); + src->htype = -1; +} + +/* will merge the given handshake_buffer_st to the handshake_recv_buffer + * list. The given hsk packet will be released in any case (success or failure). */ -int -_gnutls_handshake_io_recv_skip (gnutls_session_t session, - content_type_t type, - gnutls_handshake_description_t htype, - size_t ptr_size) +static int merge_handshake_packet(gnutls_session_t session, handshake_buffer_st* hsk) { - opaque * ptr; - int ret; +int exists = 0, i, pos = 0; +int ret; + + for (i=0;i<session->internals.handshake_recv_buffer_size;i++) + { + if (session->internals.handshake_recv_buffer[i].htype == hsk->htype) + { + exists = 1; + pos = i; + break; + } + } - if (ptr_size == 0) return 0; + if (exists == 0) + pos = session->internals.handshake_recv_buffer_size; - ptr = gnutls_malloc(ptr_size); - if (ptr == NULL) - return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + if (pos > MAX_HANDSHAKE_MSGS) + return gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS); - ret = _gnutls_handshake_io_recv_int(session, type, htype, ptr, ptr_size); - gnutls_free(ptr); + if (exists == 0) + { + if (hsk->length > 0 && hsk->end_offset > 0 && hsk->end_offset-hsk->start_offset+1 != hsk->length) + { + ret = _gnutls_buffer_resize(&hsk->data, hsk->length); + if (ret < 0) + return gnutls_assert_val(ret); + + hsk->data.length = hsk->length; + + memmove(&hsk->data.data[hsk->start_offset], hsk->data.data, hsk->end_offset-hsk->start_offset+1); + } + + session->internals.handshake_recv_buffer_size++; + _gnutls_handshake_buffer_move(&session->internals.handshake_recv_buffer[pos], hsk); - if (ret < 0) - return gnutls_assert_val(ret); + /* rewrite headers to make them look as each packet came as a single fragment */ + _gnutls_write_uint24(0, &hsk->header[6]); + _gnutls_write_uint24(hsk->length, &hsk->header[9]); + } + else + { + if (hsk->start_offset < session->internals.handshake_recv_buffer[pos].start_offset && + hsk->end_offset >= session->internals.handshake_recv_buffer[pos].start_offset) + { + memcpy(&session->internals.handshake_recv_buffer[pos].data.data[hsk->start_offset], + hsk->data.data, hsk->data.length); + session->internals.handshake_recv_buffer[pos].start_offset = hsk->start_offset; + session->internals.handshake_recv_buffer[pos].end_offset = + MIN(hsk->end_offset, session->internals.handshake_recv_buffer[pos].end_offset); + } + else if (hsk->end_offset > session->internals.handshake_recv_buffer[pos].end_offset && + hsk->start_offset <= session->internals.handshake_recv_buffer[pos].end_offset+1) + { + memcpy(&session->internals.handshake_recv_buffer[pos].data.data[hsk->start_offset], + hsk->data.data, hsk->data.length); + + session->internals.handshake_recv_buffer[pos].end_offset = hsk->end_offset; + session->internals.handshake_recv_buffer[pos].start_offset = + MIN(hsk->start_offset, session->internals.handshake_recv_buffer[pos].start_offset); + } + _gnutls_handshake_buffer_clear(hsk); + } return 0; } +#define LAST_ELEMENT (session->internals.handshake_recv_buffer_size-1) + +/* returns the last stored handshake packet. + */ +static int get_last_packet(gnutls_session_t session, gnutls_handshake_description_t htype, + handshake_buffer_st * hsk) +{ +handshake_buffer_st* recv_buf = session->internals.handshake_recv_buffer; + + if (IS_DTLS(session)) + { + if (session->internals.handshake_recv_buffer_size == 0 || + (session->internals.dtls.hsk_read_seq != recv_buf[LAST_ELEMENT].sequence)) + return gnutls_assert_val(GNUTLS_E_AGAIN); + + if (htype != recv_buf[LAST_ELEMENT].htype) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); + + else if ((recv_buf[LAST_ELEMENT].start_offset == 0 && + recv_buf[LAST_ELEMENT].end_offset == recv_buf[LAST_ELEMENT].length -1) || + recv_buf[LAST_ELEMENT].length == 0) + { + session->internals.dtls.hsk_read_seq++; + _gnutls_handshake_buffer_move(hsk, &recv_buf[LAST_ELEMENT]); + session->internals.handshake_recv_buffer_size--; + + return 0; + } + else + return gnutls_assert_val(GNUTLS_E_AGAIN); + } + else /* TLS */ + { + if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length == recv_buf[0].data.length) + { + _gnutls_handshake_buffer_move(hsk, &recv_buf[0]); + return 0; + } + else + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } +} + /* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. */ ssize_t _gnutls_handshake_io_recv_int (gnutls_session_t session, - content_type_t type, gnutls_handshake_description_t htype, - void *iptr, size_t ptr_size) + handshake_buffer_st * hsk) { - size_t left; - ssize_t i; - opaque *ptr; - size_t dsize; + gnutls_datum_t msg; + mbuffer_st* bufel = NULL, *prev = NULL; + int ret; + size_t data_size; + handshake_buffer_st* recv_buf = session->internals.handshake_recv_buffer; - ptr = iptr; - left = ptr_size; + ret = get_last_packet(session, htype, hsk); + if (ret >= 0) + return ret; - if (ptr_size == 0 || iptr == NULL) - { - gnutls_assert (); - return GNUTLS_E_INVALID_REQUEST; - } + /* if we don't have a complete message waiting for us, try + * receiving more */ + ret = _gnutls_recv_in_buffers(session, GNUTLS_HANDSHAKE, htype); + if (ret < 0) + return gnutls_assert_val(ret); + + bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); + if (bufel == NULL) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); - if (session->internals.handshake_recv_buffer.length > 0) + if (!IS_DTLS(session)) { - size_t tmp; + ssize_t remain, append, header_size; - /* if we have already received some data */ - if (ptr_size <= session->internals.handshake_recv_buffer.length) + do { - /* if requested less data then return it. + if (bufel->type != GNUTLS_HANDSHAKE) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + /* if we have a half received message the complete it. */ - gnutls_assert (); + remain = recv_buf[0].length - + recv_buf[0].data.length; - tmp = ptr_size; - _gnutls_buffer_pop_data (&session->internals.handshake_recv_buffer, - iptr, &tmp); - return tmp; - } - gnutls_assert (); + /* this is the rest of a previous message */ + if (recv_buf[0].length > 0 && remain > 0) + { + if (msg.size <= remain) + append = msg.size; + else + append = remain; + + ret = _gnutls_buffer_append_data(&recv_buf[0].data, msg.data, append); + if (ret < 0) + return gnutls_assert_val(ret); + + _mbuffer_head_remove_bytes(&session->internals.record_buffer, append); + } + else /* received new message */ + { + ret = parse_handshake_header(session, bufel, htype, &recv_buf[0]); + if (ret < 0) + return gnutls_assert_val(ret); - tmp = ptr_size; - _gnutls_buffer_pop_data (&session->internals.handshake_recv_buffer, - iptr, &tmp); - left -= tmp; - } + header_size = ret; + session->internals.handshake_recv_buffer_size = 1; - while (left > 0) + if (htype != recv_buf[0].htype) + { /* an unexpected packet */ + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); + } + + _mbuffer_set_uhead_size(bufel, header_size); + + data_size = MIN(recv_buf[0].length, _mbuffer_get_udata_size(bufel)); + ret = _gnutls_buffer_append_data(&recv_buf[0].data, _mbuffer_get_udata_ptr(bufel), data_size); + if (ret < 0) + return gnutls_assert_val(ret); + + _mbuffer_head_remove_bytes(&session->internals.record_buffer, data_size+header_size); + } + + /* if packet is complete then return it + */ + if (recv_buf[0].length == + recv_buf[0].data.length) + { + return get_last_packet(session, htype, hsk); + } + bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); + } + while(bufel != NULL); + + /* if we are here it means that the received packets were not + * enough to complete the handshake packet. + */ + return gnutls_assert_val(GNUTLS_E_AGAIN); + } + else /* DTLS */ { - dsize = ptr_size - left; - i = _gnutls_recv_int (session, type, htype, &ptr[dsize], left, NULL); - if (i < 0) - { + handshake_buffer_st tmp; - if (dsize > 0 && (i == GNUTLS_E_INTERRUPTED || i == GNUTLS_E_AGAIN)) + do + { + /* we now + * 0. parse headers + * 1. insert to handshake_recv_buffer + * 2. sort handshake_recv_buffer on sequence numbers + * 3. return first packet if completed or GNUTLS_E_AGAIN. + */ + do { - gnutls_assert (); + if (bufel->type != GNUTLS_HANDSHAKE) + { + gnutls_assert(); + goto next; /* ignore packet */ + } + + _gnutls_handshake_buffer_init(&tmp); + + ret = parse_handshake_header(session, bufel, htype, &tmp); + if (ret < 0) + { + gnutls_assert(); + _gnutls_audit_log("Invalid handshake packet headers. Discarding.\n"); + break; + } + + _mbuffer_consume(&session->internals.record_buffer, bufel, ret); + + data_size = MIN(tmp.length, tmp.end_offset-tmp.start_offset+1); + + ret = _gnutls_buffer_append_data(&tmp.data, _mbuffer_get_udata_ptr(bufel), data_size); + if (ret < 0) + return gnutls_assert_val(ret); + + _mbuffer_consume(&session->internals.record_buffer, bufel, data_size); + + ret = merge_handshake_packet(session, &tmp); + if (ret < 0) + return gnutls_assert_val(ret); - _gnutls_buffer_append_data (&session->internals. - handshake_recv_buffer, iptr, dsize); } + while(_mbuffer_get_udata_size(bufel) > 0); + + prev = bufel; + bufel = _mbuffer_dequeue(&session->internals.record_buffer, bufel); - return i; + _mbuffer_xfree(&prev); + continue; + +next: + bufel = _mbuffer_head_get_next(bufel, NULL); } - else + while(bufel != NULL); + + /* sort in descending order */ + if (session->internals.handshake_recv_buffer_size > 1) + qsort(recv_buf, session->internals.handshake_recv_buffer_size, + sizeof(recv_buf[0]), handshake_compare); + + while(session->internals.handshake_recv_buffer_size > 0 && + recv_buf[LAST_ELEMENT].sequence < session->internals.dtls.hsk_read_seq) { - if (i == 0) - break; /* EOF */ + _gnutls_audit_log("Discarded replayed handshake packet with sequence %d\n", tmp.sequence); + _gnutls_handshake_buffer_clear(&recv_buf[LAST_ELEMENT]); + session->internals.handshake_recv_buffer_size--; } - left -= i; - + return get_last_packet(session, htype, hsk); } - - session->internals.handshake_recv_buffer.length = 0; - - return ptr_size - left; } /* Buffer for handshake packets. Keeps the packets in order @@ -1005,15 +1275,3 @@ _gnutls_handshake_buffer_empty (gnutls_session_t session) return 0; } - - -int -_gnutls_handshake_buffer_clear (gnutls_session_t session) -{ - - _gnutls_buffers_log ("BUF[HSK]: Cleared Data from buffer\n"); - _gnutls_buffer_clear (&session->internals.handshake_hash_buffer); - - return 0; -} - |