[tls] Handle fragmented handshake records

Originally-implemented-by: Christopher Schenk <christopher@cschenk.net>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/930/head
Michael Brown 2023-03-30 16:28:40 +01:00
parent aa368ba529
commit 1d1cf74a5e
2 changed files with 26 additions and 18 deletions

View File

@ -398,6 +398,8 @@ struct tls_connection {
struct io_buffer rx_header_iobuf; struct io_buffer rx_header_iobuf;
/** List of received data buffers */ /** List of received data buffers */
struct list_head rx_data; struct list_head rx_data;
/** Received handshake fragment */
struct io_buffer *rx_handshake;
}; };
/** RX I/O buffer size /** RX I/O buffer size

View File

@ -388,6 +388,7 @@ static void free_tls ( struct refcnt *refcnt ) {
list_del ( &iobuf->list ); list_del ( &iobuf->list );
free_iob ( iobuf ); free_iob ( iobuf );
} }
free_iob ( tls->rx_handshake );
x509_chain_put ( tls->certs ); x509_chain_put ( tls->certs );
x509_chain_put ( tls->chain ); x509_chain_put ( tls->chain );
x509_root_put ( tls->root ); x509_root_put ( tls->root );
@ -2426,17 +2427,13 @@ static int tls_new_handshake ( struct tls_connection *tls,
/* Parse header */ /* Parse header */
if ( sizeof ( *handshake ) > remaining ) { if ( sizeof ( *handshake ) > remaining ) {
DBGC ( tls, "TLS %p received underlength Handshake\n", /* Leave remaining fragment unconsumed */
tls ); break;
DBGC_HD ( tls, handshake, remaining );
return -EINVAL_HANDSHAKE;
} }
payload_len = tls_uint24 ( &handshake->length ); payload_len = tls_uint24 ( &handshake->length );
if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) { if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) {
DBGC ( tls, "TLS %p received overlength Handshake\n", /* Leave remaining fragment unconsumed */
tls ); break;
DBGC_HD ( tls, handshake, remaining );
return -EINVAL_HANDSHAKE;
} }
payload = &handshake->payload; payload = &handshake->payload;
record_len = ( sizeof ( *handshake ) + payload_len ); record_len = ( sizeof ( *handshake ) + payload_len );
@ -2554,14 +2551,16 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type,
struct list_head *rx_data ) { struct list_head *rx_data ) {
int ( * handler ) ( struct tls_connection *tls, int ( * handler ) ( struct tls_connection *tls,
struct io_buffer *iobuf ); struct io_buffer *iobuf );
struct io_buffer *iobuf; struct io_buffer *tmp = NULL;
struct io_buffer **iobuf;
int rc; int rc;
/* Deliver data records as-is to the plainstream interface */ /* Deliver data records as-is to the plainstream interface */
if ( type == TLS_TYPE_DATA ) if ( type == TLS_TYPE_DATA )
return tls_new_data ( tls, rx_data ); return tls_new_data ( tls, rx_data );
/* Determine handler */ /* Determine handler and fragment buffer */
iobuf = &tmp;
switch ( type ) { switch ( type ) {
case TLS_TYPE_CHANGE_CIPHER: case TLS_TYPE_CHANGE_CIPHER:
handler = tls_new_change_cipher; handler = tls_new_change_cipher;
@ -2571,6 +2570,7 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type,
break; break;
case TLS_TYPE_HANDSHAKE: case TLS_TYPE_HANDSHAKE:
handler = tls_new_handshake; handler = tls_new_handshake;
iobuf = &tls->rx_handshake;
break; break;
default: default:
DBGC ( tls, "TLS %p unknown record type %d\n", tls, type ); DBGC ( tls, "TLS %p unknown record type %d\n", tls, type );
@ -2579,8 +2579,10 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type,
} }
/* Merge into a single I/O buffer */ /* 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 " DBGC ( tls, "TLS %p could not concatenate non-data record "
"type %d\n", tls, type ); "type %d\n", tls, type );
rc = -ENOMEM_RX_CONCAT; rc = -ENOMEM_RX_CONCAT;
@ -2588,19 +2590,23 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type,
} }
/* Handle record */ /* Handle record */
if ( ( rc = handler ( tls, iobuf ) ) != 0 ) if ( ( rc = handler ( tls, *iobuf ) ) != 0 )
goto err_handle; goto err_handle;
/* Sanity check */ /* Discard I/O buffer if empty */
assert ( iob_len ( iobuf ) == 0 ); if ( ! iob_len ( *iobuf ) ) {
free_iob ( *iobuf );
*iobuf = NULL;
}
/* Free I/O buffer */ /* Sanity check */
free_iob ( iobuf ); assert ( tmp == NULL );
return 0; return 0;
err_handle: err_handle:
free_iob ( iobuf ); free_iob ( *iobuf );
*iobuf = NULL;
err_concatenate: err_concatenate:
return rc; return rc;
} }