/* Takes a buffer in multiple chunks and puts all the data in a single
 * contiguous segment, ensuring that the @align_pos is 16-byte aligned.
 *
 * Returns 0 on success or an error code otherwise.
 *
 * Cost: O(n)
 * n: number of segments initially in the buffer
 */
int _mbuffer_linearize_align16(mbuffer_head_st * buf, unsigned align_pos)
{
	mbuffer_st *bufel, *cur;
	gnutls_datum_t msg;
	size_t pos = 0;

	if (buf->length == 0) {
		/* Nothing to do */
		return 0;
	}
	
	bufel = _mbuffer_head_get_first(buf, NULL);
	if (buf->length == 1 && is_aligned16(bufel, align_pos))
		return 0;

	bufel = _mbuffer_alloc_align16(buf->byte_length, align_pos);
	if (!bufel) {
		gnutls_assert();
		return GNUTLS_E_MEMORY_ERROR;
	}

	for (cur = _mbuffer_head_get_first(buf, &msg);
	     msg.data != NULL; cur = _mbuffer_head_get_next(cur, &msg)) {
		memcpy(&bufel->msg.data[pos], msg.data, msg.size);
		bufel->msg.size += msg.size;
		pos += msg.size;
	}

	_mbuffer_head_clear(buf);
	_mbuffer_enqueue(buf, bufel);

	return 0;
}
Beispiel #2
0
static ssize_t
_gnutls_stream_read(gnutls_session_t session, mbuffer_st ** bufel,
		    size_t size, gnutls_pull_func pull_func,
		    unsigned int *ms)
{
	size_t left;
	ssize_t i = 0;
	size_t max_size = max_record_recv_size(session);
	uint8_t *ptr;
	gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr;
	int ret;
	struct timespec t1, t2;
	unsigned int diff;

	session->internals.direction = 0;

	*bufel = _mbuffer_alloc_align16(MAX(max_size, size), get_total_headers(session));
	if (!*bufel) {
		gnutls_assert();
		return GNUTLS_E_MEMORY_ERROR;
	}
	ptr = (*bufel)->msg.data;

	left = size;
	while (left > 0) {
		if (ms && *ms > 0) {
			ret = _gnutls_io_check_recv(session, *ms);
			if (ret < 0) {
				gnutls_assert();
				goto cleanup;
			}

			gnutls_gettime(&t1);
		}

		reset_errno(session);

		i = pull_func(fd, &ptr[size - left], left);

		if (i < 0) {
			int err = get_errno(session);

			_gnutls_read_log
			    ("READ: %d returned from %p, errno=%d gerrno=%d\n",
			     (int) i, fd, errno,
			     session->internals.errnum);

			if (err == EAGAIN || err == EINTR) {
				if (size - left > 0) {

					_gnutls_read_log
					    ("READ: returning %d bytes from %p\n",
					     (int) (size - left), fd);

					goto finish;
				}

				ret = errno_to_gerr(err, 0);
				goto cleanup;
			} else {
				gnutls_assert();
				ret = GNUTLS_E_PULL_ERROR;
				goto cleanup;
			}
		} else {

			_gnutls_read_log("READ: Got %d bytes from %p\n",
					 (int) i, fd);

			if (i == 0)
				break;	/* EOF */
		}

		left -= i;
		(*bufel)->msg.size += i;

		if (ms && *ms > 0 && *ms != GNUTLS_INDEFINITE_TIMEOUT) {
			gnutls_gettime(&t2);
			diff = timespec_sub_ms(&t2, &t1);
			if (diff < *ms)
				*ms -= diff;
			else {
				ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT);
				goto cleanup;
			}
		}
	}

      finish:

	_gnutls_read_log("READ: read %d bytes from %p\n",
			 (int) (size - left), fd);

	if (size - left == 0)
		_mbuffer_xfree(bufel);

	return (size - left);

      cleanup:
	_mbuffer_xfree(bufel);
	return ret;
}
Beispiel #3
0
static ssize_t
_gnutls_dgram_read(gnutls_session_t session, mbuffer_st ** bufel,
		   gnutls_pull_func pull_func, unsigned int *ms)
{
	ssize_t i, ret;
	uint8_t *ptr;
	struct timespec t1, t2;
	size_t max_size, recv_size;
	gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr;
	unsigned int diff;

	max_size = max_record_recv_size(session);
	recv_size = max_size;

	session->internals.direction = 0;

	if (ms && *ms > 0) {
		ret = _gnutls_io_check_recv(session, *ms);
		if (ret < 0)
			return gnutls_assert_val(ret);
		gnutls_gettime(&t1);
	}

	*bufel = _mbuffer_alloc_align16(max_size, get_total_headers(session));
	if (*bufel == NULL)
		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);

	ptr = (*bufel)->msg.data;

	reset_errno(session);
	i = pull_func(fd, ptr, recv_size);

	if (i < 0) {
		int err = get_errno(session);

		_gnutls_read_log("READ: %d returned from %p, errno=%d\n",
				 (int) i, fd, err);

		ret = errno_to_gerr(err, 1);
		goto cleanup;
	} else {
		_gnutls_read_log("READ: Got %d bytes from %p\n", (int) i,
				 fd);
		if (i == 0) {
			/* If we get here, we likely have a stream socket.
			 * That assumption may not work on DCCP. */
			gnutls_assert();
			ret = 0;
			goto cleanup;
		}

		_mbuffer_set_udata_size(*bufel, i);
	}

	if (ms && *ms > 0) {
		gnutls_gettime(&t2);
		diff = timespec_sub_ms(&t2, &t1);
		if (diff < *ms)
			*ms -= diff;
		else {
			ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT);
			goto cleanup;
		}
	}

	_gnutls_read_log("READ: read %d bytes from %p\n", (int) i, fd);

	return i;

      cleanup:
	_mbuffer_xfree(bufel);
	return ret;
}
Beispiel #4
0
/* This function behaves exactly like write(). The only difference is
 * that it accepts, the gnutls_session_t and the content_type_t of data to
 * send (if called by the user the Content is specific)
 * It is intended to transfer data, under the current session.    
 *
 * @type: The content type to send
 * @htype: If this is a handshake message then the handshake type
 * @epoch_rel: %EPOCH_READ_* or %EPOCH_WRITE_*
 * @data: the data to be sent
 * @data_size: the size of the @data
 * @min_pad: the minimum required padding
 * @mflags: zero or %MBUFFER_FLUSH
 *
 * Oct 30 2001: Removed capability to send data more than MAX_RECORD_SIZE.
 * This makes the function much easier to read, and more error resistant
 * (there were cases were the old function could mess everything up).
 * --nmav
 *
 * This function may accept a NULL pointer for data, and 0 for size, if
 * and only if the previous send was interrupted for some reason.
 *
 */
ssize_t
_gnutls_send_tlen_int(gnutls_session_t session, content_type_t type,
		      gnutls_handshake_description_t htype,
		      unsigned int epoch_rel, const void *_data,
		      size_t data_size, size_t min_pad,
		      unsigned int mflags)
{
	mbuffer_st *bufel;
	ssize_t cipher_size;
	int retval, ret;
	int send_data_size;
	uint8_t *headers;
	int header_size;
	const uint8_t *data = _data;
	record_parameters_st *record_params;
	size_t max_send_size;
	record_state_st *record_state;

	ret = _gnutls_epoch_get(session, epoch_rel, &record_params);
	if (ret < 0)
		return gnutls_assert_val(ret);

	/* Safeguard against processing data with an incomplete cipher state. */
	if (!record_params->initialized)
		return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);

	record_state = &record_params->write;

	/* Do not allow null pointer if the send buffer is empty.
	 * If the previous send was interrupted then a null pointer is
	 * ok, and means to resume.
	 */
	if (session->internals.record_send_buffer.byte_length == 0 &&
	    (data_size == 0 && _data == NULL)) {
		gnutls_assert();
		return GNUTLS_E_INVALID_REQUEST;
	}

	if (type != GNUTLS_ALERT)	/* alert messages are sent anyway */
		if (session_is_valid(session)
		    || session->internals.may_not_write != 0) {
			gnutls_assert();
			return GNUTLS_E_INVALID_SESSION;
		}

	max_send_size = max_user_send_size(session, record_params);

	if (data_size > max_send_size) {
		if (IS_DTLS(session))
			return gnutls_assert_val(GNUTLS_E_LARGE_PACKET);

		send_data_size = max_send_size;
	} else
		send_data_size = data_size;

	/* Only encrypt if we don't have data to send 
	 * from the previous run. - probably interrupted.
	 */
	if (mflags != 0
	    && session->internals.record_send_buffer.byte_length > 0) {
		ret = _gnutls_io_write_flush(session);
		if (ret > 0)
			cipher_size = ret;
		else
			cipher_size = 0;

		retval = session->internals.record_send_buffer_user_size;
	} else {
		if (unlikely((send_data_size == 0 && min_pad == 0)))
			return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);

		/* now proceed to packet encryption
		 */
		cipher_size = MAX_RECORD_SEND_SIZE(session);

		bufel = _mbuffer_alloc_align16(cipher_size + CIPHER_SLACK_SIZE, 
			get_total_headers2(session, record_params));
		if (bufel == NULL)
			return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);

		headers = _mbuffer_get_uhead_ptr(bufel);
		headers[0] = type;
		/* Use the default record version, if it is
		 * set. */
		copy_record_version(session, htype, &headers[1]);
		/* Adjust header length and add sequence for DTLS */
		if (IS_DTLS(session))
			memcpy(&headers[3],
			       record_state->sequence_number.i, 8);

		_gnutls_record_log
		    ("REC[%p]: Preparing Packet %s(%d) with length: %d and min pad: %d\n",
		     session, _gnutls_packet2str(type), type,
		     (int) data_size, (int) min_pad);

		header_size = RECORD_HEADER_SIZE(session);
		_mbuffer_set_udata_size(bufel, cipher_size);
		_mbuffer_set_uhead_size(bufel, header_size);

		ret =
		    _gnutls_encrypt(session,
				    data, send_data_size, min_pad,
				    bufel, type, record_params);
		if (ret <= 0) {
			gnutls_assert();
			if (ret == 0)
				ret = GNUTLS_E_ENCRYPTION_FAILED;
			gnutls_free(bufel);
			return ret;	/* error */
		}

		cipher_size = _mbuffer_get_udata_size(bufel);
		retval = send_data_size;
		session->internals.record_send_buffer_user_size =
		    send_data_size;

		/* increase sequence number
		 */
		if (sequence_increment
		    (session, &record_state->sequence_number) != 0) {
			session_invalidate(session);
			gnutls_free(bufel);
			return
			    gnutls_assert_val
			    (GNUTLS_E_RECORD_LIMIT_REACHED);
		}

		ret = _gnutls_io_write_buffered(session, bufel, mflags);
	}

	if (ret != cipher_size) {
		/* If we have sent any data then just return
		 * the error value. Do not invalidate the session.
		 */
		if (ret < 0 && gnutls_error_is_fatal(ret) == 0)
			return gnutls_assert_val(ret);

		if (ret > 0)
			ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);

		session_unresumable(session);
		session->internals.may_not_write = 1;
		return gnutls_assert_val(ret);
	}

	session->internals.record_send_buffer_user_size = 0;

	_gnutls_record_log
	    ("REC[%p]: Sent Packet[%d] %s(%d) in epoch %d and length: %d\n",
	     session, (unsigned int)
	     _gnutls_uint64touint32(&record_state->sequence_number),
	     _gnutls_packet2str(type), type, (int) record_params->epoch,
	     (int) cipher_size);

	return retval;
}
Beispiel #5
0
/* @ms: is the number of milliseconds to wait for data. Use zero for indefinite.
 *
 * This will receive record layer packets and add them to 
 * application_data_buffer and handshake_data_buffer.
 *
 * If the htype is not -1 then handshake timeouts
 * will be enforced.
 */
ssize_t
_gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
			gnutls_handshake_description_t htype,
			unsigned int ms)
{
	uint64 *packet_sequence;
	gnutls_datum_t ciphertext;
	mbuffer_st *bufel = NULL, *decrypted = NULL;
	gnutls_datum_t t;
	int ret;
	unsigned int empty_fragments = 0;
	record_parameters_st *record_params;
	record_state_st *record_state;
	struct tls_record_st record;

      begin:

	if (empty_fragments >
	    session->internals.priorities.max_empty_records) {
		gnutls_assert();
		return GNUTLS_E_TOO_MANY_EMPTY_PACKETS;
	}

	if (session->internals.read_eof != 0) {
		/* if we have already read an EOF
		 */
		return 0;
	} else if (session_is_valid(session) != 0
		   || session->internals.may_not_read != 0)
		return gnutls_assert_val(GNUTLS_E_INVALID_SESSION);

	/* get the record state parameters */
	ret =
	    _gnutls_epoch_get(session, EPOCH_READ_CURRENT, &record_params);
	if (ret < 0)
		return gnutls_assert_val(ret);

	/* Safeguard against processing data with an incomplete cipher state. */
	if (!record_params->initialized)
		return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);

	record_state = &record_params->read;

	/* receive headers */
	ret = recv_headers(session, record_params, type, htype, &record, &ms);
	if (ret < 0) {
		ret = gnutls_assert_val_fatal(ret);
		goto recv_error;
	}

	if (IS_DTLS(session))
		packet_sequence = &record.sequence;
	else
		packet_sequence = &record_state->sequence_number;

	/* Read the packet data and insert it to record_recv_buffer.
	 */
	ret =
	    _gnutls_io_read_buffered(session, record.packet_size,
				     record.type, &ms);
	if (ret != record.packet_size) {
		gnutls_assert();
		goto recv_error;
	}

	/* ok now we are sure that we have read all the data - so
	 * move on !
	 */
	ret = _mbuffer_linearize_align16(&session->internals.record_recv_buffer, 
		get_total_headers2(session, record_params));
	if (ret < 0)
		return gnutls_assert_val(ret);

	bufel =
	    _mbuffer_head_get_first(&session->internals.record_recv_buffer,
				    NULL);
	if (bufel == NULL)
		return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);

	/* We allocate the maximum possible to allow few compressed bytes to expand to a
	 * full record. Moreover we add space for any pad and the MAC (in case
	 * they are encrypted).
	 */
	ret = max_decrypted_size(session) + MAX_PAD_SIZE + MAX_HASH_SIZE;
	decrypted = _mbuffer_alloc_align16(ret, 0);
	if (decrypted == NULL)
		return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);

	_mbuffer_set_udata_size(decrypted, ret);
	ciphertext.data =
	    (uint8_t *) _mbuffer_get_udata_ptr(bufel) + record.header_size;
	ciphertext.size = record.length;

	/* decrypt the data we got. 
	 */
	t.data = _mbuffer_get_udata_ptr(decrypted);
	t.size = _mbuffer_get_udata_size(decrypted);
	ret =
	    _gnutls_decrypt(session, &ciphertext, &t,
			    record.type, record_params, packet_sequence);
	if (ret >= 0)
		_mbuffer_set_udata_size(decrypted, ret);

	_mbuffer_head_remove_bytes(&session->internals.record_recv_buffer,
				   record.header_size + record.length);
	if (ret < 0) {
		gnutls_assert();
		_gnutls_audit_log(session,
				  "Discarded message[%u] due to invalid decryption\n",
				  (unsigned int)
				  _gnutls_uint64touint32(packet_sequence));
		goto sanity_check_error;
	}

	/* check for duplicates. We check after the message
	 * is processed and authenticated to avoid someone
	 * messing with our windows.
	 */
	if (IS_DTLS(session)
	    && session->internals.no_replay_protection == 0) {
		ret = _dtls_record_check(record_params, packet_sequence);
		if (ret < 0) {
			_gnutls_record_log
			    ("REC[%p]: Discarded duplicate message[%u.%u]: %s\n",
			     session,
			     (unsigned int) record.sequence.i[0] * 256 +
			     (unsigned int) record.sequence.i[1],
			     (unsigned int)
			     _gnutls_uint64touint32(packet_sequence),
			     _gnutls_packet2str(record.type));
			goto sanity_check_error;
		}
		_gnutls_record_log
		    ("REC[%p]: Decrypted Packet[%u.%u] %s(%d) with length: %d\n",
		     session,
		     (unsigned int) record.sequence.i[0] * 256 +
		     (unsigned int) record.sequence.i[1],
		     (unsigned int)
		     _gnutls_uint64touint32(packet_sequence),
		     _gnutls_packet2str(record.type), record.type,
		     (int) _mbuffer_get_udata_size(decrypted));
	} else {
		_gnutls_record_log
		    ("REC[%p]: Decrypted Packet[%u] %s(%d) with length: %d\n",
		     session,
		     (unsigned int)
		     _gnutls_uint64touint32(packet_sequence),
		     _gnutls_packet2str(record.type), record.type,
		     (int) _mbuffer_get_udata_size(decrypted));
	}

	/* increase sequence number 
	 */
	if (!IS_DTLS(session)
	    && sequence_increment(session,
				  &record_state->sequence_number) != 0) {
		session_invalidate(session);
		gnutls_assert();
		ret = GNUTLS_E_RECORD_LIMIT_REACHED;
		goto sanity_check_error;
	}

/* (originally for) TLS 1.0 CBC protection. 
 * Actually this code is called if we just received
 * an empty packet. An empty TLS packet is usually
 * sent to protect some vulnerabilities in the CBC mode.
 * In that case we go to the beginning and start reading
 * the next packet.
 */
	if (_mbuffer_get_udata_size(decrypted) == 0) {
		_mbuffer_xfree(&decrypted);
		empty_fragments++;
		goto begin;
	}

	if (record.v2) {
		decrypted->htype = GNUTLS_HANDSHAKE_CLIENT_HELLO_V2;
	} else {
		uint8_t *p = _mbuffer_get_udata_ptr(decrypted);
		decrypted->htype = p[0];
	}

	ret =
	    record_add_to_buffers(session, &record, type, htype,
				  packet_sequence, decrypted);

	/* decrypted is now either deinitialized or buffered somewhere else */

	if (ret < 0)
		return gnutls_assert_val(ret);

	return ret;

      discard:
	session->internals.dtls.packets_dropped++;

	/* discard the whole received fragment. */
	bufel =
	    _mbuffer_head_pop_first(&session->internals.
				    record_recv_buffer);
	_mbuffer_xfree(&bufel);
	return gnutls_assert_val(GNUTLS_E_AGAIN);

      sanity_check_error:
	if (IS_DTLS(session)) {
		session->internals.dtls.packets_dropped++;
		ret = gnutls_assert_val(GNUTLS_E_AGAIN);
		goto cleanup;
	}

	session_unresumable(session);
	session_invalidate(session);

      cleanup:
	_mbuffer_xfree(&decrypted);
	return ret;

      recv_error:
	if (ret < 0
	    && (gnutls_error_is_fatal(ret) == 0
		|| ret == GNUTLS_E_TIMEDOUT))
		return ret;

	if (type == GNUTLS_ALERT) {	/* we were expecting close notify */
		session_invalidate(session);
		gnutls_assert();
		return 0;
	}

	if (IS_DTLS(session) && (ret == GNUTLS_E_DECRYPTION_FAILED ||
		ret == GNUTLS_E_UNSUPPORTED_VERSION_PACKET ||
		ret == GNUTLS_E_UNEXPECTED_PACKET_LENGTH ||
		ret == GNUTLS_E_UNEXPECTED_PACKET ||
		ret == GNUTLS_E_ERROR_IN_FINISHED_PACKET ||
		ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET)) {
		goto discard;
	}

	session_invalidate(session);
	session_unresumable(session);

	if (ret == 0)
		return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
	else
		return ret;
}