summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Brown2012-09-26 22:42:23 +0200
committerMichael Brown2012-09-27 02:56:01 +0200
commit72db14640c2a9eac0ba53baa955b180f1f4b9c2f (patch)
tree239f9dbbdfe5c889a9fd72110efae604ec80b14c
parent[crypto] Allow in-place CBC decryption (diff)
downloadipxe-72db14640c2a9eac0ba53baa955b180f1f4b9c2f.tar.gz
ipxe-72db14640c2a9eac0ba53baa955b180f1f4b9c2f.tar.xz
ipxe-72db14640c2a9eac0ba53baa955b180f1f4b9c2f.zip
[tls] Split received records over multiple I/O buffers
TLS servers are not obliged to implement the RFC3546 maximum fragment length extension, and many common servers (including OpenSSL, as used in Apache's mod_ssl) do not do so. iPXE may therefore have to cope with TLS records of up to 16kB. Allocations for 16kB have a non-negligible chance of failing, causing the TLS connection to abort. Fix by maintaining the received record as a linked list of I/O buffers, rather than a single contiguous buffer. To reduce memory pressure, we also decrypt in situ, and deliver the decrypted data via xfer_deliver_iob() rather than xfer_deliver_raw(). Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/include/ipxe/tls.h30
-rw-r--r--src/net/tls.c442
2 files changed, 304 insertions, 168 deletions
diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h
index 2af864df..f8a75409 100644
--- a/src/include/ipxe/tls.h
+++ b/src/include/ipxe/tls.h
@@ -19,6 +19,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/sha256.h>
#include <ipxe/x509.h>
#include <ipxe/pending.h>
+#include <ipxe/iobuf.h>
/** A TLS header */
struct tls_header {
@@ -264,14 +265,35 @@ struct tls_session {
uint64_t rx_seq;
/** RX state */
enum tls_rx_state rx_state;
- /** Offset within current RX state */
- size_t rx_rcvd;
/** Current received record header */
struct tls_header rx_header;
- /** Current received raw data buffer */
- void *rx_data;
+ /** Current received record header (static I/O buffer) */
+ struct io_buffer rx_header_iobuf;
+ /** List of received data buffers */
+ struct list_head rx_data;
};
+/** RX I/O buffer size
+ *
+ * The maximum fragment length extension is optional, and many common
+ * implementations (including OpenSSL) do not support it. We must
+ * therefore be prepared to receive records of up to 16kB in length.
+ * The chance of an allocation of this size failing is non-negligible,
+ * so we must split received data into smaller allocations.
+ */
+#define TLS_RX_BUFSIZE 4096
+
+/** Minimum RX I/O buffer size
+ *
+ * To simplify manipulations, we ensure that no RX I/O buffer is
+ * smaller than this size. This allows us to assume that the MAC and
+ * padding are entirely contained within the final I/O buffer.
+ */
+#define TLS_RX_MIN_BUFSIZE 512
+
+/** RX I/O buffer alignment */
+#define TLS_RX_ALIGN 16
+
extern int add_tls ( struct interface *xfer, const char *name,
struct interface **next );
diff --git a/src/net/tls.c b/src/net/tls.c
index ec2763b5..45b7e525 100644
--- a/src/net/tls.c
+++ b/src/net/tls.c
@@ -101,6 +101,14 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define EINFO_EINVAL_RX_STATE \
__einfo_uniqify ( EINFO_EINVAL, 0x0c, \
"Invalid receive state" )
+#define EINVAL_MAC __einfo_error ( EINFO_EINVAL_MAC )
+#define EINFO_EINVAL_MAC \
+ __einfo_uniqify ( EINFO_EINVAL, 0x0d, \
+ "Invalid MAC" )
+#define EINVAL_NON_DATA __einfo_error ( EINFO_EINVAL_NON_DATA )
+#define EINFO_EINVAL_NON_DATA \
+ __einfo_uniqify ( EINFO_EINVAL, 0x0e, \
+ "Overlength non-data record" )
#define EIO_ALERT __einfo_error ( EINFO_EIO_ALERT )
#define EINFO_EIO_ALERT \
__einfo_uniqify ( EINFO_EINVAL, 0x01, \
@@ -125,10 +133,6 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define EINFO_ENOMEM_TX_CIPHERTEXT \
__einfo_uniqify ( EINFO_ENOMEM, 0x05, \
"Not enough space for transmitted ciphertext" )
-#define ENOMEM_RX_PLAINTEXT __einfo_error ( EINFO_ENOMEM_RX_PLAINTEXT )
-#define EINFO_ENOMEM_RX_PLAINTEXT \
- __einfo_uniqify ( EINFO_ENOMEM, 0x06, \
- "Not enough space for received plaintext" )
#define ENOMEM_RX_DATA __einfo_error ( EINFO_ENOMEM_RX_DATA )
#define EINFO_ENOMEM_RX_DATA \
__einfo_uniqify ( EINFO_ENOMEM, 0x07, \
@@ -159,7 +163,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
"Handshake verification failed" )
#define EPROTO_VERSION __einfo_error ( EINFO_EPROTO_VERSION )
#define EINFO_EPROTO_VERSION \
- __einfo_uniqify ( EINFO_EINVAL, 0x01, \
+ __einfo_uniqify ( EINFO_EPROTO, 0x01, \
"Illegal protocol version upgrade" )
static int tls_send_plaintext ( struct tls_session *tls, unsigned int type,
@@ -295,13 +299,18 @@ struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = {
static void free_tls ( struct refcnt *refcnt ) {
struct tls_session *tls =
container_of ( refcnt, struct tls_session, refcnt );
+ struct io_buffer *iobuf;
+ struct io_buffer *tmp;
/* Free dynamically-allocated resources */
tls_clear_cipher ( tls, &tls->tx_cipherspec );
tls_clear_cipher ( tls, &tls->tx_cipherspec_pending );
tls_clear_cipher ( tls, &tls->rx_cipherspec );
tls_clear_cipher ( tls, &tls->rx_cipherspec_pending );
- free ( tls->rx_data );
+ list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) {
+ list_del ( &iobuf->list );
+ free_iob ( iobuf );
+ }
x509_chain_put ( tls->chain );
/* Free TLS structure itself */
@@ -1013,7 +1022,7 @@ static int tls_send_client_hello ( struct tls_session *tls ) {
hello.extensions.max_fragment_length_len
= htons ( sizeof ( hello.extensions.max_fragment_length ) );
hello.extensions.max_fragment_length.max
- = TLS_MAX_FRAGMENT_LENGTH_2048;
+ = TLS_MAX_FRAGMENT_LENGTH_4096;
return tls_send_handshake ( tls, &hello, sizeof ( hello ) );
}
@@ -1703,31 +1712,71 @@ static int tls_new_handshake ( struct tls_session *tls,
*
* @v tls TLS session
* @v type Record type
- * @v data Plaintext record
- * @v len Length of plaintext record
+ * @v rx_data List of received data buffers
* @ret rc Return status code
*/
static int tls_new_record ( struct tls_session *tls, unsigned int type,
- const void *data, size_t len ) {
+ struct list_head *rx_data ) {
+ struct io_buffer *iobuf;
+ int ( * handler ) ( struct tls_session *tls, const void *data,
+ size_t len );
+ int rc;
+
+ /* Deliver data records to the plainstream interface */
+ if ( type == TLS_TYPE_DATA ) {
+
+ /* Fail unless we are ready to receive data */
+ if ( ! tls_ready ( tls ) )
+ return -ENOTCONN;
+ /* Deliver each I/O buffer in turn */
+ while ( ( iobuf = list_first_entry ( rx_data, struct io_buffer,
+ list ) ) ) {
+ list_del ( &iobuf->list );
+ if ( ( rc = xfer_deliver_iob ( &tls->plainstream,
+ iobuf ) ) != 0 ) {
+ DBGC ( tls, "TLS %p could not deliver data: "
+ "%s\n", tls, strerror ( rc ) );
+ return rc;
+ }
+ }
+ return 0;
+ }
+
+ /* For all other records, fail unless we have exactly one I/O buffer */
+ iobuf = list_first_entry ( rx_data, struct io_buffer, list );
+ assert ( iobuf != NULL );
+ list_del ( &iobuf->list );
+ if ( ! list_empty ( rx_data ) ) {
+ DBGC ( tls, "TLS %p overlength non-data record\n", tls );
+ return -EINVAL_NON_DATA;
+ }
+
+ /* Determine handler */
switch ( type ) {
case TLS_TYPE_CHANGE_CIPHER:
- return tls_new_change_cipher ( tls, data, len );
+ handler = tls_new_change_cipher;
+ break;
case TLS_TYPE_ALERT:
- return tls_new_alert ( tls, data, len );
+ handler = tls_new_alert;
+ break;
case TLS_TYPE_HANDSHAKE:
- return tls_new_handshake ( tls, data, len );
- case TLS_TYPE_DATA:
- if ( ! tls_ready ( tls ) )
- return -ENOTCONN;
- return xfer_deliver_raw ( &tls->plainstream, data, len );
+ handler = tls_new_handshake;
+ break;
default:
/* RFC4346 says that we should just ignore unknown
* record types.
*/
+ handler = NULL;
DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type );
- return 0;
+ break;
}
+
+ /* Handle record and free I/O buffer */
+ if ( handler )
+ rc = handler ( tls, iobuf->data, iob_len ( iobuf ) );
+ free_iob ( iobuf );
+ return rc;
}
/******************************************************************************
@@ -1738,9 +1787,56 @@ static int tls_new_record ( struct tls_session *tls, unsigned int type,
*/
/**
+ * Initialise HMAC
+ *
+ * @v cipherspec Cipher specification
+ * @v ctx Context
+ * @v seq Sequence number
+ * @v tlshdr TLS header
+ */
+static void tls_hmac_init ( struct tls_cipherspec *cipherspec, void *ctx,
+ uint64_t seq, struct tls_header *tlshdr ) {
+ struct digest_algorithm *digest = cipherspec->suite->digest;
+
+ hmac_init ( digest, ctx, cipherspec->mac_secret, &digest->digestsize );
+ seq = cpu_to_be64 ( seq );
+ hmac_update ( digest, ctx, &seq, sizeof ( seq ) );
+ hmac_update ( digest, ctx, tlshdr, sizeof ( *tlshdr ) );
+}
+
+/**
+ * Update HMAC
+ *
+ * @v cipherspec Cipher specification
+ * @v ctx Context
+ * @v data Data
+ * @v len Length of data
+ */
+static void tls_hmac_update ( struct tls_cipherspec *cipherspec, void *ctx,
+ const void *data, size_t len ) {
+ struct digest_algorithm *digest = cipherspec->suite->digest;
+
+ hmac_update ( digest, ctx, data, len );
+}
+
+/**
+ * Finalise HMAC
+ *
+ * @v cipherspec Cipher specification
+ * @v ctx Context
+ * @v mac HMAC to fill in
+ */
+static void tls_hmac_final ( struct tls_cipherspec *cipherspec, void *ctx,
+ void *hmac ) {
+ struct digest_algorithm *digest = cipherspec->suite->digest;
+
+ hmac_final ( digest, ctx, cipherspec->mac_secret,
+ &digest->digestsize, hmac );
+}
+
+/**
* Calculate HMAC
*
- * @v tls TLS session
* @v cipherspec Cipher specification
* @v seq Sequence number
* @v tlshdr TLS header
@@ -1748,21 +1844,15 @@ static int tls_new_record ( struct tls_session *tls, unsigned int type,
* @v len Length of data
* @v mac HMAC to fill in
*/
-static void tls_hmac ( struct tls_session *tls __unused,
- struct tls_cipherspec *cipherspec,
+static void tls_hmac ( struct tls_cipherspec *cipherspec,
uint64_t seq, struct tls_header *tlshdr,
const void *data, size_t len, void *hmac ) {
struct digest_algorithm *digest = cipherspec->suite->digest;
- uint8_t digest_ctx[digest->ctxsize];
+ uint8_t ctx[digest->ctxsize];
- hmac_init ( digest, digest_ctx, cipherspec->mac_secret,
- &digest->digestsize );
- seq = cpu_to_be64 ( seq );
- hmac_update ( digest, digest_ctx, &seq, sizeof ( seq ) );
- hmac_update ( digest, digest_ctx, tlshdr, sizeof ( *tlshdr ) );
- hmac_update ( digest, digest_ctx, data, len );
- hmac_final ( digest, digest_ctx, cipherspec->mac_secret,
- &digest->digestsize, hmac );
+ tls_hmac_init ( cipherspec, ctx, seq, tlshdr );
+ tls_hmac_update ( cipherspec, ctx, data, len );
+ tls_hmac_final ( cipherspec, ctx, hmac );
}
/**
@@ -1877,8 +1967,7 @@ static int tls_send_plaintext ( struct tls_session *tls, unsigned int type,
plaintext_tlshdr.length = htons ( len );
/* Calculate MAC */
- tls_hmac ( tls, cipherspec, tls->tx_seq, &plaintext_tlshdr,
- data, len, mac );
+ tls_hmac ( cipherspec, tls->tx_seq, &plaintext_tlshdr, data, len, mac );
/* Allocate and assemble plaintext struct */
if ( is_stream_cipher ( cipher ) ) {
@@ -1945,36 +2034,25 @@ static int tls_send_plaintext ( struct tls_session *tls, unsigned int type,
* Split stream-ciphered record into data and MAC portions
*
* @v tls TLS session
- * @v plaintext Plaintext record
- * @v plaintext_len Length of record
- * @ret data Data
- * @ret len Length of data
- * @ret digest MAC digest
+ * @v rx_data List of received data buffers
+ * @v mac MAC to fill in
* @ret rc Return status code
*/
static int tls_split_stream ( struct tls_session *tls,
- void *plaintext, size_t plaintext_len,
- void **data, size_t *len, void **digest ) {
- void *content;
- size_t content_len;
- void *mac;
- size_t mac_len;
-
- /* Decompose stream-ciphered data */
- mac_len = tls->rx_cipherspec.suite->digest->digestsize;
- if ( plaintext_len < mac_len ) {
- DBGC ( tls, "TLS %p received underlength record\n", tls );
- DBGC_HD ( tls, plaintext, plaintext_len );
+ struct list_head *rx_data, void **mac ) {
+ size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize;
+ struct io_buffer *iobuf;
+
+ /* Extract MAC */
+ iobuf = list_last_entry ( rx_data, struct io_buffer, list );
+ assert ( iobuf != NULL );
+ if ( iob_len ( iobuf ) < mac_len ) {
+ DBGC ( tls, "TLS %p received underlength MAC\n", tls );
+ DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) );
return -EINVAL_STREAM;
}
- content_len = ( plaintext_len - mac_len );
- content = plaintext;
- mac = ( content + content_len );
-
- /* Fill in return values */
- *data = content;
- *len = content_len;
- *digest = mac;
+ iob_unput ( iobuf, mac_len );
+ *mac = iobuf->tail;
return 0;
}
@@ -1983,65 +2061,56 @@ static int tls_split_stream ( struct tls_session *tls,
* Split block-ciphered record into data and MAC portions
*
* @v tls TLS session
- * @v plaintext Plaintext record
- * @v plaintext_len Length of record
- * @ret data Data
- * @ret len Length of data
- * @ret digest MAC digest
+ * @v rx_data List of received data buffers
+ * @v mac MAC to fill in
* @ret rc Return status code
*/
static int tls_split_block ( struct tls_session *tls,
- void *plaintext, size_t plaintext_len,
- void **data, size_t *len,
- void **digest ) {
- void *iv;
+ struct list_head *rx_data, void **mac ) {
+ size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize;
+ struct io_buffer *iobuf;
size_t iv_len;
- void *content;
- size_t content_len;
- void *mac;
- size_t mac_len;
- void *padding;
+ uint8_t *padding_final;
+ uint8_t *padding;
size_t padding_len;
- unsigned int i;
-
- /* Sanity check */
- if ( plaintext_len < 1 ) {
- DBGC ( tls, "TLS %p received underlength record\n", tls );
- DBGC_HD ( tls, plaintext, plaintext_len );
- return -EINVAL_BLOCK;
- }
/* TLSv1.1 and later use an explicit IV */
+ iobuf = list_first_entry ( rx_data, struct io_buffer, list );
iv_len = ( ( tls->version >= TLS_VERSION_TLS_1_1 ) ?
tls->rx_cipherspec.suite->cipher->blocksize : 0 );
-
- /* Decompose block-ciphered data */
- mac_len = tls->rx_cipherspec.suite->digest->digestsize;
- padding_len = *( ( uint8_t * ) ( plaintext + plaintext_len - 1 ) );
- if ( plaintext_len < ( iv_len + mac_len + padding_len + 1 ) ) {
- DBGC ( tls, "TLS %p received underlength record\n", tls );
- DBGC_HD ( tls, plaintext, plaintext_len );
+ if ( iob_len ( iobuf ) < iv_len ) {
+ DBGC ( tls, "TLS %p received underlength IV\n", tls );
+ DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) );
return -EINVAL_BLOCK;
}
- content_len = ( plaintext_len - iv_len - mac_len - padding_len - 1 );
- iv = plaintext;
- content = ( iv + iv_len );
- mac = ( content + content_len );
- padding = ( mac + mac_len );
-
- /* Verify padding bytes */
- for ( i = 0 ; i < padding_len ; i++ ) {
- if ( *( ( uint8_t * ) ( padding + i ) ) != padding_len ) {
+ iob_pull ( iobuf, iv_len );
+
+ /* Extract and verify padding */
+ iobuf = list_last_entry ( rx_data, struct io_buffer, list );
+ padding_final = ( iobuf->tail - 1 );
+ padding_len = *padding_final;
+ if ( ( padding_len + 1 ) > iob_len ( iobuf ) ) {
+ DBGC ( tls, "TLS %p received underlength padding\n", tls );
+ DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) );
+ return -EINVAL_BLOCK;
+ }
+ iob_unput ( iobuf, ( padding_len + 1 ) );
+ for ( padding = iobuf->tail ; padding < padding_final ; padding++ ) {
+ if ( *padding != padding_len ) {
DBGC ( tls, "TLS %p received bad padding\n", tls );
- DBGC_HD ( tls, plaintext, plaintext_len );
+ DBGC_HD ( tls, padding, padding_len );
return -EINVAL_PADDING;
}
}
- /* Fill in return values */
- *data = content;
- *len = content_len;
- *digest = mac;
+ /* Extract MAC */
+ if ( iob_len ( iobuf ) < mac_len ) {
+ DBGC ( tls, "TLS %p received underlength MAC\n", tls );
+ DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) );
+ return -EINVAL_BLOCK;
+ }
+ iob_unput ( iobuf, mac_len );
+ *mac = iobuf->tail;
return 0;
}
@@ -2051,71 +2120,65 @@ static int tls_split_block ( struct tls_session *tls,
*
* @v tls TLS session
* @v tlshdr Record header
- * @v ciphertext Ciphertext record
+ * @v rx_data List of received data buffers
* @ret rc Return status code
*/
static int tls_new_ciphertext ( struct tls_session *tls,
struct tls_header *tlshdr,
- const void *ciphertext ) {
+ struct list_head *rx_data ) {
struct tls_header plaintext_tlshdr;
struct tls_cipherspec *cipherspec = &tls->rx_cipherspec;
struct cipher_algorithm *cipher = cipherspec->suite->cipher;
- size_t record_len = ntohs ( tlshdr->length );
- void *plaintext = NULL;
- void *data;
- size_t len;
+ struct digest_algorithm *digest = cipherspec->suite->digest;
+ uint8_t ctx[digest->ctxsize];
+ uint8_t verify_mac[digest->digestsize];
+ struct io_buffer *iobuf;
void *mac;
- size_t mac_len = cipherspec->suite->digest->digestsize;
- uint8_t verify_mac[mac_len];
+ size_t len = 0;
int rc;
- /* Allocate buffer for plaintext */
- plaintext = malloc ( record_len );
- if ( ! plaintext ) {
- DBGC ( tls, "TLS %p could not allocate %zd bytes for "
- "decryption buffer\n", tls, record_len );
- rc = -ENOMEM_RX_PLAINTEXT;
- goto done;
+ /* Decrypt the received data */
+ list_for_each_entry ( iobuf, &tls->rx_data, list ) {
+ cipher_decrypt ( cipher, cipherspec->cipher_ctx,
+ iobuf->data, iobuf->data, iob_len ( iobuf ) );
}
- /* Decrypt the record */
- cipher_decrypt ( cipher, cipherspec->cipher_ctx,
- ciphertext, plaintext, record_len );
-
/* Split record into content and MAC */
if ( is_stream_cipher ( cipher ) ) {
- if ( ( rc = tls_split_stream ( tls, plaintext, record_len,
- &data, &len, &mac ) ) != 0 )
- goto done;
+ if ( ( rc = tls_split_stream ( tls, rx_data, &mac ) ) != 0 )
+ return rc;
} else {
- if ( ( rc = tls_split_block ( tls, plaintext, record_len,
- &data, &len, &mac ) ) != 0 )
- goto done;
+ if ( ( rc = tls_split_block ( tls, rx_data, &mac ) ) != 0 )
+ return rc;
+ }
+
+ /* Calculate total length */
+ DBGC2 ( tls, "Received plaintext data:\n" );
+ list_for_each_entry ( iobuf, rx_data, list ) {
+ DBGC2_HD ( tls, iobuf->data, iob_len ( iobuf ) );
+ len += iob_len ( iobuf );
}
/* Verify MAC */
plaintext_tlshdr.type = tlshdr->type;
plaintext_tlshdr.version = tlshdr->version;
plaintext_tlshdr.length = htons ( len );
- tls_hmac ( tls, cipherspec, tls->rx_seq, &plaintext_tlshdr,
- data, len, verify_mac);
- if ( memcmp ( mac, verify_mac, mac_len ) != 0 ) {
+ tls_hmac_init ( cipherspec, ctx, tls->rx_seq, &plaintext_tlshdr );
+ list_for_each_entry ( iobuf, rx_data, list ) {
+ tls_hmac_update ( cipherspec, ctx, iobuf->data,
+ iob_len ( iobuf ) );
+ }
+ tls_hmac_final ( cipherspec, ctx, verify_mac );
+ if ( memcmp ( mac, verify_mac, sizeof ( verify_mac ) ) != 0 ) {
DBGC ( tls, "TLS %p failed MAC verification\n", tls );
- DBGC_HD ( tls, plaintext, record_len );
- goto done;
+ return -EINVAL_MAC;
}
- DBGC2 ( tls, "Received plaintext data:\n" );
- DBGC2_HD ( tls, data, len );
-
/* Process plaintext record */
- if ( ( rc = tls_new_record ( tls, tlshdr->type, data, len ) ) != 0 )
- goto done;
+ if ( ( rc = tls_new_record ( tls, tlshdr->type, rx_data ) ) != 0 )
+ return rc;
- rc = 0;
- done:
- free ( plaintext );
- return rc;
+ return 0;
}
/******************************************************************************
@@ -2195,20 +2258,61 @@ static struct interface_descriptor tls_plainstream_desc =
*/
static int tls_newdata_process_header ( struct tls_session *tls ) {
size_t data_len = ntohs ( tls->rx_header.length );
+ size_t remaining = data_len;
+ size_t frag_len;
+ struct io_buffer *iobuf;
+ struct io_buffer *tmp;
+ int rc;
- /* Allocate data buffer now that we know the length */
- assert ( tls->rx_data == NULL );
- tls->rx_data = malloc ( data_len );
- if ( ! tls->rx_data ) {
- DBGC ( tls, "TLS %p could not allocate %zd bytes "
- "for receive buffer\n", tls, data_len );
- return -ENOMEM_RX_DATA;
+ /* Allocate data buffers now that we know the length */
+ assert ( list_empty ( &tls->rx_data ) );
+ while ( remaining ) {
+
+ /* Calculate fragment length. Ensure that no block is
+ * smaller than TLS_RX_MIN_BUFSIZE (by increasing the
+ * allocation length if necessary).
+ */
+ frag_len = remaining;
+ if ( frag_len > TLS_RX_BUFSIZE )
+ frag_len = TLS_RX_BUFSIZE;
+ remaining -= frag_len;
+ if ( remaining < TLS_RX_MIN_BUFSIZE ) {
+ frag_len += remaining;
+ remaining = 0;
+ }
+
+ /* Allocate buffer */
+ iobuf = alloc_iob_raw ( frag_len, TLS_RX_ALIGN, 0 );
+ if ( ! iobuf ) {
+ DBGC ( tls, "TLS %p could not allocate %zd of %zd "
+ "bytes for receive buffer\n", tls,
+ remaining, data_len );
+ rc = -ENOMEM_RX_DATA;
+ goto err;
+ }
+
+ /* Ensure tailroom is exactly what we asked for. This
+ * will result in unaligned I/O buffers when the
+ * fragment length is unaligned, which can happen only
+ * before we switch to using a block cipher.
+ */
+ iob_reserve ( iobuf, ( iob_tailroom ( iobuf ) - frag_len ) );
+
+ /* Add I/O buffer to list */
+ list_add_tail ( &iobuf->list, &tls->rx_data );
}
/* Move to data state */
tls->rx_state = TLS_RX_DATA;
return 0;
+
+ err:
+ list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) {
+ list_del ( &iobuf->list );
+ free_iob ( iobuf );
+ }
+ return rc;
}
/**
@@ -2218,22 +2322,31 @@ static int tls_newdata_process_header ( struct tls_session *tls ) {
* @ret rc Returned status code
*/
static int tls_newdata_process_data ( struct tls_session *tls ) {
+ struct io_buffer *iobuf;
int rc;
+ /* Move current buffer to end of list */
+ iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list );
+ list_del ( &iobuf->list );
+ list_add_tail ( &iobuf->list, &tls->rx_data );
+
+ /* Continue receiving data if any space remains */
+ iobuf = list_first_entry ( &tls->rx_data, struct io_buffer, list );
+ if ( iob_tailroom ( iobuf ) )
+ return 0;
+
/* Process record */
if ( ( rc = tls_new_ciphertext ( tls, &tls->rx_header,
- tls->rx_data ) ) != 0 )
+ &tls->rx_data ) ) != 0 )
return rc;
/* Increment RX sequence number */
tls->rx_seq += 1;
- /* Free data buffer */
- free ( tls->rx_data );
- tls->rx_data = NULL;
-
/* Return to header state */
+ assert ( list_empty ( &tls->rx_data ) );
tls->rx_state = TLS_RX_HEADER;
+ iob_unput ( &tls->rx_header_iobuf, sizeof ( tls->rx_header ) );
return 0;
}
@@ -2250,22 +2363,22 @@ static int tls_cipherstream_deliver ( struct tls_session *tls,
struct io_buffer *iobuf,
struct xfer_metadata *xfer __unused ) {
size_t frag_len;
- void *buf;
- size_t buf_len;
int ( * process ) ( struct tls_session *tls );
+ struct io_buffer *dest;
int rc;
while ( iob_len ( iobuf ) ) {
+
/* Select buffer according to current state */
switch ( tls->rx_state ) {
case TLS_RX_HEADER:
- buf = &tls->rx_header;
- buf_len = sizeof ( tls->rx_header );
+ dest = &tls->rx_header_iobuf;
process = tls_newdata_process_header;
break;
case TLS_RX_DATA:
- buf = tls->rx_data;
- buf_len = ntohs ( tls->rx_header.length );
+ dest = list_first_entry ( &tls->rx_data,
+ struct io_buffer, list );
+ assert ( dest != NULL );
process = tls_newdata_process_data;
break;
default:
@@ -2275,20 +2388,18 @@ static int tls_cipherstream_deliver ( struct tls_session *tls,
}
/* Copy data portion to buffer */
- frag_len = ( buf_len - tls->rx_rcvd );
- if ( frag_len > iob_len ( iobuf ) )
- frag_len = iob_len ( iobuf );
- memcpy ( ( buf + tls->rx_rcvd ), iobuf->data, frag_len );
- tls->rx_rcvd += frag_len;
+ frag_len = iob_len ( iobuf );
+ if ( frag_len > iob_tailroom ( dest ) )
+ frag_len = iob_tailroom ( dest );
+ memcpy ( iob_put ( dest, frag_len ), iobuf->data, frag_len );
iob_pull ( iobuf, frag_len );
/* Process data if buffer is now full */
- if ( tls->rx_rcvd == buf_len ) {
+ if ( iob_tailroom ( dest ) == 0 ) {
if ( ( rc = process ( tls ) ) != 0 ) {
tls_close ( tls, rc );
goto done;
}
- tls->rx_rcvd = 0;
}
}
rc = 0;
@@ -2521,6 +2632,9 @@ int add_tls ( struct interface *xfer, const char *name,
tls->handshake_digest = &sha256_algorithm;
tls->handshake_ctx = tls->handshake_sha256_ctx;
tls->tx_pending = TLS_TX_CLIENT_HELLO;
+ iob_populate ( &tls->rx_header_iobuf, &tls->rx_header, 0,
+ sizeof ( tls->rx_header ) );
+ INIT_LIST_HEAD ( &tls->rx_data );
/* Add pending operations for server and client Finished messages */
pending_get ( &tls->client_negotiation );