quic conformance: 10.2.1 rate limiting

Implement the two requirements about limiting closing transmission size to
no more than thrice the received size.

Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21429)
This commit is contained in:
Pauli 2023-07-18 11:37:14 +10:00
parent afe4a7978d
commit 50e76846bf
4 changed files with 75 additions and 5 deletions

View File

@ -56,7 +56,8 @@ typedef struct ossl_quic_tx_packetiser_args_st {
* crypto[QUIC_PN_SPACE_APP] is the 1-RTT crypto stream.
*/
QUIC_SSTREAM *crypto[QUIC_PN_SPACE_NUM];
} OSSL_QUIC_TX_PACKETISER_ARGS;
} OSSL_QUIC_TX_PACKETISER_ARGS;
typedef struct ossl_quic_tx_packetiser_st OSSL_QUIC_TX_PACKETISER;
@ -67,6 +68,14 @@ typedef void (ossl_quic_initial_token_free_fn)(const unsigned char *buf,
void ossl_quic_tx_packetiser_free(OSSL_QUIC_TX_PACKETISER *txp);
/*
* When in the closing state we need to maintain a count of received bytes
* so that we can limit the number of close connection frames we send.
* Refer RFC 9000 s. 10.2.1 Closing Connection State.
*/
void ossl_quic_tx_packetiser_record_received_closing_bytes(
OSSL_QUIC_TX_PACKETISER *txp, size_t n);
/*
* Generates a datagram by polling the various ELs to determine if they want to
* generate any frames, and generating a datagram which coalesces packets for

View File

@ -1783,6 +1783,7 @@ static void ch_rx_check_forged_pkt_limit(QUIC_CHANNEL *ch)
static int ch_rx(QUIC_CHANNEL *ch)
{
int handled_any = 0;
const int closing = ossl_quic_channel_is_closing(ch);
if (!ch->is_server && !ch->have_sent_any_pkt)
/*
@ -1797,6 +1798,11 @@ static int ch_rx(QUIC_CHANNEL *ch)
if (!ossl_qrx_read_pkt(ch->qrx, &ch->qrx_pkt))
break;
/* Track the amount of data received while in the closing state */
if (closing)
ossl_quic_tx_packetiser_record_received_closing_bytes(
ch->txp, ch->qrx_pkt->hdr->len);
if (!handled_any)
ch_update_idle(ch);
@ -1820,7 +1826,7 @@ static int ch_rx(QUIC_CHANNEL *ch)
* When in TERMINATING - CLOSING, generate a CONN_CLOSE frame whenever we
* process one or more incoming packets.
*/
if (handled_any && ossl_quic_channel_is_closing(ch))
if (handled_any && closing)
ch->conn_close_queued = 1;
return 1;
@ -1858,8 +1864,12 @@ static void ch_rx_handle_packet(QUIC_CHANNEL *ch)
assert(ch->qrx_pkt != NULL);
/*
* RFC 9000 s. 10.2.1 Closing Connection State:
* An endpoint that is closing is not required to process any
* received frame.
*/
if (!ossl_quic_channel_is_active(ch))
/* Do not process packets once we are terminating. */
return;
if (ossl_quic_pkt_type_is_encrypted(ch->qrx_pkt->hdr->type)) {

View File

@ -202,7 +202,7 @@ struct quic_channel_st {
*/
uint64_t txku_threshold_override;
/* Valid if we are in the TERMINATING or TERMINATED states. */
/* Valid if we are in the TERMINATING or TERMINATED states. */
QUIC_TERMINATE_CAUSE terminate_cause;
/*

View File

@ -89,6 +89,13 @@ struct ossl_quic_tx_packetiser_st {
OSSL_QUIC_FRAME_CONN_CLOSE conn_close_frame;
/*
* Counts of the number of bytes received and sent while in the closing
* state.
*/
uint64_t closing_bytes_recv;
uint64_t closing_bytes_xmit;
/* Internal state - packet assembly. */
struct txp_el {
unsigned char *scratch; /* scratch buffer for packet assembly */
@ -1640,6 +1647,47 @@ static void on_sstream_updated(uint64_t stream_id, void *arg)
ossl_quic_stream_map_update_state(txp->args.qsm, s);
}
/*
* Returns 1 if we can send that many bytes in closing state, 0 otherwise.
* Also maintains the bytes sent state if it returns a success.
*/
static int try_commit_conn_close(OSSL_QUIC_TX_PACKETISER *txp, size_t n)
{
int res;
/* We can always send the first connection close frame */
if (txp->closing_bytes_recv == 0)
return 1;
/*
* RFC 9000 s. 10.2.1 Closing Connection State:
* To avoid being used for an amplification attack, such
* endpoints MUST limit the cumulative size of packets it sends
* to three times the cumulative size of the packets that are
* received and attributed to the connection.
* and:
* An endpoint in the closing state MUST either discard packets
* received from an unvalidated address or limit the cumulative
* size of packets it sends to an unvalidated address to three
* times the size of packets it receives from that address.
*/
res = txp->closing_bytes_xmit + n <= txp->closing_bytes_recv * 3;
/*
* Attribute the bytes to the connection, if we are allowed to send them
* and this isn't the first closing frame.
*/
if (res && txp->closing_bytes_recv != 0)
txp->closing_bytes_xmit += n;
return res;
}
void ossl_quic_tx_packetiser_record_received_closing_bytes(
OSSL_QUIC_TX_PACKETISER *txp, size_t n)
{
txp->closing_bytes_recv += n;
}
static int txp_generate_pre_token(OSSL_QUIC_TX_PACKETISER *txp,
struct txp_pkt *pkt,
int chosen_for_conn_close,
@ -1692,6 +1740,7 @@ static int txp_generate_pre_token(OSSL_QUIC_TX_PACKETISER *txp,
if (a->allow_conn_close && txp->want_conn_close && chosen_for_conn_close) {
WPACKET *wpkt = tx_helper_begin(h);
OSSL_QUIC_FRAME_CONN_CLOSE f, *pf = &txp->conn_close_frame;
size_t l;
if (wpkt == NULL)
return 0;
@ -1721,7 +1770,9 @@ static int txp_generate_pre_token(OSSL_QUIC_TX_PACKETISER *txp,
pf->reason_len = 0;
}
if (ossl_quic_wire_encode_frame_conn_close(wpkt, pf)) {
if (ossl_quic_wire_encode_frame_conn_close(wpkt, pf)
&& WPACKET_get_total_written(wpkt, &l)
&& try_commit_conn_close(txp, l)) {
if (!tx_helper_commit(h))
return 0;