Skip to content

Commit

Permalink
quic: support fragmented TLS handshake messages
Browse files Browse the repository at this point in the history
- Fixes conn leak in fd_quic_connect if ClientHello fails
- Indirect CRYPTO frames through a new defrag buffer
- Remove fd_quic_tls_hs state machine
- Remove need to "kick start" client quic_tls_hs
  • Loading branch information
riptl authored and ripatel-fd committed Dec 19, 2024
1 parent 055cb19 commit e274acb
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 144 deletions.
119 changes: 58 additions & 61 deletions src/waltz/quic/fd_quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -2760,68 +2760,74 @@ fd_quic_frame_handle_crypto_frame( void * vp_context,
fd_quic_crypto_frame_t * crypto,
uchar const * p,
ulong p_sz ) {
/* copy the context locally */
fd_quic_frame_context_t context = *(fd_quic_frame_context_t*)vp_context;
fd_quic_frame_context_t const * context = vp_context;
context->pkt->ack_flag |= ACK_FLAG_RQD;

/* determine whether any of the data was already provided */
fd_quic_conn_t * conn = context.conn;
uint enc_level = context.pkt->enc_level;
fd_quic_conn_t * conn = context->conn;
fd_quic_tls_hs_t * tls_hs = conn->tls_hs;
uint enc_level = context->pkt->enc_level;

/* offset expected */
ulong exp_offset = conn->rx_crypto_offset[enc_level];
ulong rcv_offset = crypto->offset;
ulong rcv_sz = crypto->length;
ulong rcv_off = crypto->offset; /* in [0,2^62-1] */
ulong rcv_sz = crypto->length; /* in [0,2^62-1] */
ulong rcv_hi = rcv_off + rcv_sz; /* in [0,2^63-1] */

if( FD_UNLIKELY( rcv_sz > p_sz ) ) return FD_QUIC_PARSE_FAIL;
if( FD_UNLIKELY( rcv_sz > p_sz ) ) {
return FD_QUIC_PARSE_FAIL;
}

if( !conn->tls_hs ) {
if( !tls_hs ) {
/* Handshake already completed. Ignore frame */
/* TODO consider aborting conn if too many unsolicited crypto frames arrive */
} else if( FD_UNLIKELY( rcv_offset > exp_offset ) ) {
/* if data arrived early, we could buffer, but for now we simply won't ack */
/* TODO buffer handshake data */
return FD_QUIC_PARSE_FAIL;
} else if( FD_UNLIKELY( rcv_offset + rcv_sz <= exp_offset ) ) {
/* the full range of bytes has already been consumed, so just fall thru */
} else {
/* We have bytes that we can use */
ulong skip = 0;
if( rcv_offset < exp_offset ) skip = exp_offset - rcv_offset;

rcv_sz -= skip;
uchar const * crypto_data = p + skip;

int provide_rc = fd_quic_tls_provide_data( conn->tls_hs,
context.pkt->enc_level,
crypto_data,
rcv_sz );
if( provide_rc == FD_QUIC_FAILED ) {
/* if TLS fails, ABORT connection */

/* if TLS returns an error, we present that as reason:
FD_QUIC_CONN_REASON_CRYPTO_BASE + tls-alert
otherwise, send INTERNAL_ERROR */
uint alert = conn->tls_hs->alert;
if( alert == 0u ) {
fd_quic_frame_error( &context, FD_QUIC_CONN_REASON_INTERNAL_ERROR, __LINE__ );
} else {
fd_quic_frame_error( &context, FD_QUIC_CONN_REASON_CRYPTO_BASE + alert, __LINE__ );
}
return rcv_sz;
}

/* don't process any more frames on this connection */
return FD_QUIC_PARSE_FAIL;
}
if( enc_level < tls_hs->rx_enc_level ) {
return rcv_sz;
}

/* successful, update rx_crypto_offset */
conn->rx_crypto_offset[enc_level] += rcv_sz;
if( enc_level > tls_hs->rx_enc_level ) {
/* Discard data from any previous handshake level. Currently only
happens at the Initial->Handshake encryption level change. */
tls_hs->rx_enc_level = (uchar)enc_level;
tls_hs->rx_off = 0;
tls_hs->rx_sz = 0;
}

/* ack-eliciting */
context.pkt->ack_flag |= ACK_FLAG_RQD;
if( rcv_off > tls_hs->rx_sz ) {
context->pkt->ack_flag |= ACK_FLAG_CANCEL;
return rcv_sz;
}

if( rcv_hi < tls_hs->rx_off ) {
return rcv_sz;
}

(void)context; (void)p; (void)p_sz;
if( rcv_hi > FD_QUIC_TLS_RX_DATA_SZ ) {
fd_quic_frame_error( context, FD_QUIC_CONN_REASON_CRYPTO_BUFFER_EXCEEDED, __LINE__ );
return FD_QUIC_PARSE_FAIL;
}

tls_hs->rx_sz = (ushort)rcv_hi;
fd_memcpy( tls_hs->rx_hs_buf + rcv_off, p, rcv_sz );

int provide_rc = fd_quic_tls_process( conn->tls_hs );
if( provide_rc == FD_QUIC_FAILED ) {
/* if TLS fails, ABORT connection */

/* if TLS returns an error, we present that as reason:
FD_QUIC_CONN_REASON_CRYPTO_BASE + tls-alert
otherwise, send INTERNAL_ERROR */
uint alert = conn->tls_hs->alert;
if( alert == 0u ) {
fd_quic_frame_error( context, FD_QUIC_CONN_REASON_INTERNAL_ERROR, __LINE__ );
} else {
fd_quic_frame_error( context, FD_QUIC_CONN_REASON_CRYPTO_BASE + alert, __LINE__ );
}
return FD_QUIC_PARSE_FAIL;
}

/* no "additional" bytes - all already accounted for */
return rcv_sz;
}

Expand Down Expand Up @@ -4227,18 +4233,12 @@ fd_quic_connect( fd_quic_t * quic,
(void*)conn,
0 /*is_server*/,
tp );
quic->metrics.hs_created_cnt++;

/* Initiate the handshake */
int process_rc = fd_quic_tls_provide_data( tls_hs, FD_TLS_LEVEL_INITIAL, NULL, 0UL );
if( FD_UNLIKELY( process_rc == FD_QUIC_FAILED ) ) {
FD_DEBUG( FD_LOG_DEBUG(( "Initial fd_quic_tls_provide_data failed" )) );

/* We haven't sent any data to the peer yet,
so simply clean up and fail */
if( FD_UNLIKELY( tls_hs->alert ) ) {
FD_LOG_WARNING(( "fd_quic_tls_hs_client_new failed" ));
goto fail_tls_hs;
}

quic->metrics.hs_created_cnt++;
conn->tls_hs = tls_hs;

fd_quic_gen_initial_secret_and_keys( conn, &peer_conn_id );
Expand All @@ -4256,7 +4256,7 @@ fd_quic_connect( fd_quic_t * quic,
/* shut down tls_hs */
fd_quic_tls_hs_delete( tls_hs );
fd_quic_tls_hs_pool_ele_release( state->hs_pool, tls_hs );

fd_quic_conn_free( quic, conn );
return NULL;
}

Expand Down Expand Up @@ -4365,9 +4365,6 @@ fd_quic_conn_create( fd_quic_t * quic,
fd_memset( conn->last_pkt_number, 0, sizeof( conn->last_pkt_number ) );
fd_memset( conn->pkt_number, 0, sizeof( conn->pkt_number ) );

/* crypto offset for first packet always starts at 0 */
fd_memset( conn->rx_crypto_offset, 0, sizeof( conn->rx_crypto_offset ) );

/* TODO lots of fd_memset calls that should really be builtin memset */
fd_memset( conn->hs_sent_bytes, 0, sizeof( conn->hs_sent_bytes ) );
fd_memset( conn->hs_ackd_bytes, 0, sizeof( conn->hs_ackd_bytes ) );
Expand Down
8 changes: 0 additions & 8 deletions src/waltz/quic/fd_quic_conn.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,6 @@ struct fd_quic_conn {
uint hs_data_empty : 1; /* has all hs_data been consumed? */
fd_quic_tls_hs_t * tls_hs;

/* expected handshake data offset - one per encryption level
data received lower than this on a new packet is a protocol error
duplicate packets should already have been dropped
data received higher than this would be a gap
ignore at present, assuming will be resent in order */
ulong rx_crypto_offset[4]; /* expected handshake data (crypto) offset
one per encryption level */

/* amount of handshake data already sent from head of queue */
ulong hs_sent_bytes[4];

Expand Down
21 changes: 15 additions & 6 deletions src/waltz/quic/tests/test_quic_tls_hs.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ my_transport_params( void * context,
FD_TEST( 0==memcmp( quic_tp, test_tp, quic_tp_sz ) );
}

static void
fd_quic_tls_provide_data( fd_quic_tls_hs_t * tls_hs,
uint enc_level,
uchar const * msg,
ulong msg_sz ) {
FD_TEST( msg_sz<=FD_QUIC_TLS_RX_DATA_SZ );
tls_hs->rx_enc_level = (uchar)enc_level;
tls_hs->rx_sz = (ushort)msg_sz;
tls_hs->rx_off = 0;
fd_memcpy( tls_hs->rx_hs_buf, msg, msg_sz );
fd_quic_tls_process( tls_hs );
}

int
main( int argc,
char ** argv ) {
Expand Down Expand Up @@ -114,9 +127,6 @@ main( int argc,
// start client handshake
// client fd_quic_tls_hs_t is primed upon creation

int provide_rc = fd_quic_tls_provide_data( hs_client, FD_TLS_LEVEL_INITIAL, NULL, 0UL );
FD_TEST( provide_rc==FD_QUIC_SUCCESS );

FD_LOG_INFO(( "entering main handshake loop" ));

for( int l=0; l<16; ++l ) {
Expand All @@ -143,8 +153,7 @@ main( int argc,
// collect fragments at the same enc/sec level, then encrypt
// ... then decrypt and forward

int provide_rc = fd_quic_tls_provide_data( hs_server, hs_data->enc_level, hs_data->data, hs_data->data_sz );
FD_TEST( provide_rc!=FD_QUIC_FAILED );
fd_quic_tls_provide_data( hs_server, hs_data->enc_level, hs_data->data, hs_data->data_sz );

// remove hs_data from head of list
//tls_client->hs_data = hs_data->next;
Expand All @@ -167,7 +176,7 @@ main( int argc,
FD_LOG_DEBUG(( "provide quic data server->client" ));

// here we need encrypt/decrypt
FD_TEST( fd_quic_tls_provide_data( hs_client, hs_data->enc_level, hs_data->data, hs_data->data_sz )!=FD_QUIC_FAILED );
fd_quic_tls_provide_data( hs_client, hs_data->enc_level, hs_data->data, hs_data->data_sz );

// remove hs_data from head of list
fd_quic_tls_pop_hs_data( hs_server, hs_data->enc_level );
Expand Down
69 changes: 26 additions & 43 deletions src/waltz/quic/tls/fd_quic_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ fd_quic_tls_hs_new( fd_quic_tls_hs_t * self,
self->is_server = is_server;
self->is_flush = 0;
self->context = context;
self->state = FD_QUIC_TLS_HS_STATE_NEED_INPUT;

/* initialize handshake data */

Expand All @@ -185,61 +184,53 @@ fd_quic_tls_hs_new( fd_quic_tls_hs_t * self,
/* all handshake offsets start at zero */
fd_memset( self->hs_data_offset, 0, sizeof( self->hs_data_offset ) );

/* Set QUIC transport params */
self->self_transport_params = *self_transport_params;

if( is_server ) {
fd_tls_estate_srv_new( &self->hs.srv );
} else {
fd_tls_estate_cli_new( &self->hs.cli );
long res = fd_tls_client_handshake( &quic_tls->tls, &self->hs.cli, NULL, 0UL, 0 );
if( FD_UNLIKELY( res<0L ) ) {
self->alert = (uint)-res;
}
}

/* Set QUIC transport params */
self->self_transport_params = *self_transport_params;

return self;
}

void
fd_quic_tls_hs_delete( fd_quic_tls_hs_t * self ) {
if( !self ) return;

self->state = FD_QUIC_TLS_HS_STATE_DEAD;

if( self->is_server )
fd_tls_estate_srv_delete( &self->hs.srv );
else
fd_tls_estate_cli_delete( &self->hs.cli );
}

int
fd_quic_tls_provide_data( fd_quic_tls_hs_t * self,
uint enc_level,
uchar const * data,
ulong data_sz ) {

switch( self->state ) {
case FD_QUIC_TLS_HS_STATE_DEAD:
case FD_QUIC_TLS_HS_STATE_COMPLETE:
return FD_QUIC_SUCCESS;
default:
break;
}
fd_quic_tls_process( fd_quic_tls_hs_t * self ) {

/* Client needs to initiate the handshake */
if( FD_UNLIKELY( self->hs.base.state==FD_TLS_HS_FAIL ) ) return FD_QUIC_FAILED;
if( self->hs.base.state==FD_TLS_HS_CONNECTED ) return FD_QUIC_SUCCESS;

if( ( self->hs.base.state == FD_TLS_HS_START ) &
( !self->is_server ) ) {
long res = fd_tls_client_handshake( &self->quic_tls->tls, &self->hs.cli, NULL, 0UL, 0 );
if( FD_UNLIKELY( res<0L ) ) {
self->alert = (uint)-res;
return FD_QUIC_FAILED;
}
return FD_QUIC_SUCCESS;
}
/* Process all fully received messages */

/* QUIC-TLS allows coalescing multiple records into the same CRYPTO
frame. It also allows fragmentation, but we don't support that. */
uint enc_level = self->rx_enc_level;
for(;;) {
uchar const * buf = self->rx_hs_buf;
ulong off = self->rx_off;
ulong avail = self->rx_sz - off;
if( avail<4 ) break;

do {
long res = fd_tls_handshake( &self->quic_tls->tls, &self->hs, data, data_sz, enc_level );
/* Peek the message size from fd_tls_msg_hdr_t
?? AA BB CC => 0xCCBBAA?? => 0x??AABBCC => 0x00AABBCC */
uint msg_sz = fd_uint_bswap( FD_LOAD( uint, buf+off ) ) & 0xFFFFFFU;
if( avail<msg_sz+4 ) break;

long res = fd_tls_handshake( &self->quic_tls->tls, &self->hs, buf+off, avail, enc_level );

if( FD_UNLIKELY( res<0L ) ) {
int alert = (int)-res;
Expand All @@ -251,27 +242,19 @@ fd_quic_tls_provide_data( fd_quic_tls_hs_t * self,
return FD_QUIC_FAILED;
}

data += (ulong)res;
data_sz -= (ulong)res;
} while( data_sz );
self->rx_off = (ushort)( off+(ulong)res );
}

switch( self->hs.base.state ) {
case FD_TLS_HS_CONNECTED:
/* handshake completed */
self->is_hs_complete = 1;
self->quic_tls->handshake_complete_cb( self, self->context );
self->state = FD_QUIC_TLS_HS_STATE_COMPLETE;
return FD_QUIC_SUCCESS;
case FD_TLS_HS_FAIL:
/* handshake permanently failed */
self->state = FD_QUIC_TLS_HS_STATE_DEAD;
return FD_QUIC_FAILED;
default:
/* fd_quic_tls_provide_data will perform as much handshaking as
possible. Thus, we know that we are blocked on needing more data
when we reach fd_quic_tls_process without having completed the
handshake. */
self->state = FD_QUIC_TLS_HS_STATE_NEED_INPUT;
/* handshake not yet complete */
return FD_QUIC_SUCCESS;
}
}
Expand Down
Loading

0 comments on commit e274acb

Please sign in to comment.