[tls] Handle fragmented handshake records

Originally-implemented-by: Christopher Schenk <christopher@cschenk.net>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h
index 99c7be0..30bb1c4 100644
--- a/src/include/ipxe/tls.h
+++ b/src/include/ipxe/tls.h
@@ -398,6 +398,8 @@
 	struct io_buffer rx_header_iobuf;
 	/** List of received data buffers */
 	struct list_head rx_data;
+	/** Received handshake fragment */
+	struct io_buffer *rx_handshake;
 };
 
 /** RX I/O buffer size
diff --git a/src/net/tls.c b/src/net/tls.c
index 272ced2..000a8a7 100644
--- a/src/net/tls.c
+++ b/src/net/tls.c
@@ -388,6 +388,7 @@
 		list_del ( &iobuf->list );
 		free_iob ( iobuf );
 	}
+	free_iob ( tls->rx_handshake );
 	x509_chain_put ( tls->certs );
 	x509_chain_put ( tls->chain );
 	x509_root_put ( tls->root );
@@ -2426,17 +2427,13 @@
 
 		/* Parse header */
 		if ( sizeof ( *handshake ) > remaining ) {
-			DBGC ( tls, "TLS %p received underlength Handshake\n",
-			       tls );
-			DBGC_HD ( tls, handshake, remaining );
-			return -EINVAL_HANDSHAKE;
+			/* Leave remaining fragment unconsumed */
+			break;
 		}
 		payload_len = tls_uint24 ( &handshake->length );
 		if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) {
-			DBGC ( tls, "TLS %p received overlength Handshake\n",
-			       tls );
-			DBGC_HD ( tls, handshake, remaining );
-			return -EINVAL_HANDSHAKE;
+			/* Leave remaining fragment unconsumed */
+			break;
 		}
 		payload = &handshake->payload;
 		record_len = ( sizeof ( *handshake ) + payload_len );
@@ -2554,14 +2551,16 @@
 			    struct list_head *rx_data ) {
 	int ( * handler ) ( struct tls_connection *tls,
 			    struct io_buffer *iobuf );
-	struct io_buffer *iobuf;
+	struct io_buffer *tmp = NULL;
+	struct io_buffer **iobuf;
 	int rc;
 
 	/* Deliver data records as-is to the plainstream interface */
 	if ( type == TLS_TYPE_DATA )
 		return tls_new_data ( tls, rx_data );
 
-	/* Determine handler */
+	/* Determine handler and fragment buffer */
+	iobuf = &tmp;
 	switch ( type ) {
 	case TLS_TYPE_CHANGE_CIPHER:
 		handler = tls_new_change_cipher;
@@ -2571,6 +2570,7 @@
 		break;
 	case TLS_TYPE_HANDSHAKE:
 		handler = tls_new_handshake;
+		iobuf = &tls->rx_handshake;
 		break;
 	default:
 		DBGC ( tls, "TLS %p unknown record type %d\n", tls, type );
@@ -2579,8 +2579,10 @@
 	}
 
 	/* Merge into a single I/O buffer */
-	iobuf = iob_concatenate ( rx_data );
-	if ( ! iobuf ) {
+	if ( *iobuf )
+		list_add ( &(*iobuf)->list, rx_data );
+	*iobuf = iob_concatenate ( rx_data );
+	if ( ! *iobuf ) {
 		DBGC ( tls, "TLS %p could not concatenate non-data record "
 		       "type %d\n", tls, type );
 		rc = -ENOMEM_RX_CONCAT;
@@ -2588,19 +2590,23 @@
 	}
 
 	/* Handle record */
-	if ( ( rc = handler ( tls, iobuf ) ) != 0 )
+	if ( ( rc = handler ( tls, *iobuf ) ) != 0 )
 		goto err_handle;
 
-	/* Sanity check */
-	assert ( iob_len ( iobuf ) == 0 );
+	/* Discard I/O buffer if empty */
+	if ( ! iob_len ( *iobuf ) ) {
+		free_iob ( *iobuf );
+		*iobuf = NULL;
+	}
 
-	/* Free I/O buffer */
-	free_iob ( iobuf );
+	/* Sanity check */
+	assert ( tmp == NULL );
 
 	return 0;
 
  err_handle:
-	free_iob ( iobuf );
+	free_iob ( *iobuf );
+	*iobuf = NULL;
  err_concatenate:
 	return rc;
 }