Add the ability to mutate QUIC packets before they are written

We add callbacks so that QUIC packets can be modified by the test
framework before they are encrypted and written to the network. This
enables us to simulate badly behaving endpoints.

Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20030)
This commit is contained in:
Matt Caswell 2022-11-29 16:04:23 +00:00 committed by Hugo Landau
parent fdd4716dd6
commit 14e3140939
6 changed files with 102 additions and 20 deletions

View File

@ -73,6 +73,12 @@ QUIC_CHANNEL *ossl_quic_channel_new(const QUIC_CHANNEL_ARGS *args);
/* No-op if ch is NULL. */
void ossl_quic_channel_free(QUIC_CHANNEL *ch);
/* Set mutator callbacks for test framework support */
int ossl_quic_channel_set_mutator(QUIC_CHANNEL *ch,
ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb,
void *mutatearg);
/*
* Connection Lifecycle Events
* ===========================

View File

@ -21,8 +21,22 @@
* QUIC Record Layer - TX
* ======================
*/
typedef struct ossl_qtx_iovec_st {
const unsigned char *buf;
size_t buf_len;
} OSSL_QTX_IOVEC;
typedef struct ossl_qtx_st OSSL_QTX;
typedef int (*ossl_mutate_packet_cb)(const QUIC_PKT_HDR *hdrin,
const OSSL_QTX_IOVEC *iovecin, size_t numin,
QUIC_PKT_HDR **hdrout,
const OSSL_QTX_IOVEC **iovecout,
size_t *numout,
void *arg);
typedef void (*ossl_finish_mutate_cb)(void *arg);
typedef struct ossl_qtx_args_st {
OSSL_LIB_CTX *libctx;
const char *propq;
@ -40,6 +54,10 @@ OSSL_QTX *ossl_qtx_new(const OSSL_QTX_ARGS *args);
/* Frees the QTX. */
void ossl_qtx_free(OSSL_QTX *qtx);
/* Set mutator callbacks for test framework support */
void ossl_qtx_set_mutator(OSSL_QTX *qtx, ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb, void *mutatearg);
/*
* Secret Management
* -----------------
@ -114,10 +132,6 @@ uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id);
* Packet Transmission
* -------------------
*/
typedef struct ossl_qtx_iovec_st {
const unsigned char *buf;
size_t buf_len;
} OSSL_QTX_IOVEC;
typedef struct ossl_qtx_pkt_st {
/* Logical packet header to be serialized. */

View File

@ -41,6 +41,12 @@ QUIC_TSERVER *ossl_quic_tserver_new(const QUIC_TSERVER_ARGS *args,
void ossl_quic_tserver_free(QUIC_TSERVER *srv);
/* Set mutator callbacks for test framework support */
int ossl_quic_tserver_set_mutator(QUIC_TSERVER *srv,
ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb,
void *mutatearg);
/* Advances the state machine. */
int ossl_quic_tserver_tick(QUIC_TSERVER *srv);

View File

@ -347,6 +347,19 @@ void ossl_quic_channel_free(QUIC_CHANNEL *ch)
OPENSSL_free(ch);
}
/* Set mutator callbacks for test framework support */
int ossl_quic_channel_set_mutator(QUIC_CHANNEL *ch,
ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb,
void *mutatearg)
{
if (ch->qtx == NULL)
return 0;
ossl_qtx_set_mutator(ch->qtx, mutatecb, finishmutatecb, mutatearg);
return 1;
}
int ossl_quic_channel_get_peer_addr(QUIC_CHANNEL *ch, BIO_ADDR *peer_addr)
{
*peer_addr = ch->cur_peer_addr;

View File

@ -90,6 +90,10 @@ struct ossl_qtx_st {
* confidentiality limit.
*/
uint64_t epoch_pkt_count;
ossl_mutate_packet_cb mutatecb;
ossl_finish_mutate_cb finishmutatecb;
void *mutatearg;
};
/* Instantiates a new QTX. */
@ -141,6 +145,15 @@ void ossl_qtx_free(OSSL_QTX *qtx)
OPENSSL_free(qtx);
}
/* Set mutator callbacks for test framework support */
void ossl_qtx_set_mutator(OSSL_QTX *qtx, ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb, void *mutatearg)
{
qtx->mutatecb = mutatecb;
qtx->finishmutatecb = finishmutatecb;
qtx->mutatearg = mutatearg;
}
int ossl_qtx_provide_secret(OSSL_QTX *qtx,
uint32_t enc_level,
uint32_t suite_id,
@ -414,7 +427,7 @@ int ossl_qtx_calculate_plaintext_payload_len(OSSL_QTX *qtx, uint32_t enc_level,
*/
#define QTX_FAIL_INSUFFICIENT_LEN (-2)
static int qtx_write_hdr(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
static int qtx_write_hdr(OSSL_QTX *qtx, const QUIC_PKT_HDR *hdr, TXE *txe,
QUIC_PKT_HDR_PTRS *ptrs)
{
WPACKET wpkt;
@ -424,8 +437,8 @@ static int qtx_write_hdr(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
txe->alloc_len - txe->data_len, 0))
return 0;
if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, pkt->hdr->dst_conn_id.id_len,
pkt->hdr, ptrs)
if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, hdr->dst_conn_id.id_len,
hdr, ptrs)
|| !WPACKET_get_total_written(&wpkt, &l)) {
WPACKET_finish(&wpkt);
return 0;
@ -534,6 +547,9 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
QUIC_PKT_HDR_PTRS ptrs;
unsigned char *hdr_start;
OSSL_QRL_ENC_LEVEL *el = NULL;
QUIC_PKT_HDR *hdr;
const OSSL_QTX_IOVEC *iovec;
size_t num_iovec;
/*
* Determine if the packet needs encryption and the minimum conceivable
@ -558,8 +574,25 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
goto err;
}
/* Set some fields in the header we are responsible for. */
if (pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
pkt->hdr->key_phase = (unsigned char)(el->key_epoch & 1);
/* If we are running tests then mutate_packet may be non NULL */
if (qtx->mutatecb != NULL) {
if (!qtx->mutatecb(pkt->hdr, pkt->iovec, pkt->num_iovec, &hdr,
&iovec, &num_iovec, qtx->mutatearg)) {
ret = QTX_FAIL_GENERIC;
goto err;
}
} else {
hdr = pkt->hdr;
iovec = pkt->iovec;
num_iovec = pkt->num_iovec;
}
/* Walk the iovecs to determine actual input payload length. */
iovec_cur_init(&cur, pkt->iovec, pkt->num_iovec);
iovec_cur_init(&cur, iovec, num_iovec);
if (cur.bytes_remaining == 0) {
/* No zero-length payloads allowed. */
@ -573,10 +606,10 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
: cur.bytes_remaining;
/* Determine header length. */
pkt->hdr->data = NULL;
pkt->hdr->len = payload_len;
pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(pkt->hdr->dst_conn_id.id_len,
pkt->hdr);
hdr->data = NULL;
hdr->len = payload_len;
pred_hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(hdr->dst_conn_id.id_len,
hdr);
if (pred_hdr_len == 0) {
ret = QTX_FAIL_GENERIC;
goto err;
@ -590,14 +623,10 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
goto err;
}
/* Set some fields in the header we are responsible for. */
if (pkt->hdr->type == QUIC_PKT_TYPE_1RTT)
pkt->hdr->key_phase = (unsigned char)(el->key_epoch & 1);
if (ossl_quic_pkt_type_has_pn(pkt->hdr->type)) {
if (ossl_quic_pkt_type_has_pn(hdr->type)) {
if (!ossl_quic_wire_encode_pkt_hdr_pn(pkt->pn,
pkt->hdr->pn,
pkt->hdr->pn_len)) {
hdr->pn,
hdr->pn_len)) {
ret = QTX_FAIL_GENERIC;
goto err;
}
@ -605,7 +634,7 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
/* Append the header to the TXE. */
hdr_start = txe_data(txe) + txe->data_len;
if (!qtx_write_hdr(qtx, pkt, txe, &ptrs)) {
if (!qtx_write_hdr(qtx, hdr, txe, &ptrs)) {
ret = QTX_FAIL_GENERIC;
goto err;
}
@ -638,6 +667,8 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
assert(txe->data_len - orig_data_len == pkt_len);
}
if (qtx->finishmutatecb != NULL)
qtx->finishmutatecb(qtx->mutatearg);
return 1;
err:
@ -646,6 +677,8 @@ err:
* TXE.
*/
txe->data_len = orig_data_len;
if (qtx->finishmutatecb != NULL)
qtx->finishmutatecb(qtx->mutatearg);
return ret;
}

View File

@ -122,6 +122,16 @@ void ossl_quic_tserver_free(QUIC_TSERVER *srv)
OPENSSL_free(srv);
}
/* Set mutator callbacks for test framework support */
int ossl_quic_tserver_set_mutator(QUIC_TSERVER *srv,
ossl_mutate_packet_cb mutatecb,
ossl_finish_mutate_cb finishmutatecb,
void *mutatearg)
{
return ossl_quic_channel_set_mutator(srv->ch, mutatecb, finishmutatecb,
mutatearg);
}
int ossl_quic_tserver_tick(QUIC_TSERVER *srv)
{
ossl_quic_reactor_tick(ossl_quic_channel_get_reactor(srv->ch));