From: Matt Caswell Date: Mon, 23 Oct 2023 11:16:44 +0000 (+0100) Subject: Add a test for retries when sending app data X-Git-Tag: openssl-3.2.0-beta1~23 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=dbbdb940d421daca4a65e765b5244bde6aed3f61;p=thirdparty%2Fopenssl.git Add a test for retries when sending app data Reviewed-by: Hugo Landau Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/22473) --- diff --git a/test/helpers/ssltestlib.c b/test/helpers/ssltestlib.c index 3ae3e7d4eaf..ddc6bb7b64a 100644 --- a/test/helpers/ssltestlib.c +++ b/test/helpers/ssltestlib.c @@ -29,13 +29,15 @@ static int tls_dump_gets(BIO *bp, char *buf, int size); static int tls_dump_puts(BIO *bp, const char *str); /* Choose a sufficiently large type likely to be unused for this custom BIO */ -#define BIO_TYPE_TLS_DUMP_FILTER (0x80 | BIO_TYPE_FILTER) +#define BIO_TYPE_TLS_DUMP_FILTER (0x80 | BIO_TYPE_FILTER) #define BIO_TYPE_MEMPACKET_TEST 0x81 #define BIO_TYPE_ALWAYS_RETRY 0x82 +#define BIO_TYPE_MAYBE_RETRY (0x83 | BIO_TYPE_FILTER) static BIO_METHOD *method_tls_dump = NULL; static BIO_METHOD *meth_mem = NULL; static BIO_METHOD *meth_always_retry = NULL; +static BIO_METHOD *meth_maybe_retry = NULL; static int retry_err = -1; /* Note: Not thread safe! */ @@ -804,6 +806,100 @@ static int always_retry_puts(BIO *bio, const char *str) return retry_err; } +struct maybe_retry_data_st { + unsigned int retrycnt; +}; + +static int maybe_retry_new(BIO *bi); +static int maybe_retry_free(BIO *a); +static int maybe_retry_write(BIO *b, const char *in, int inl); +static long maybe_retry_ctrl(BIO *b, int cmd, long num, void *ptr); + +const BIO_METHOD *bio_s_maybe_retry(void) +{ + if (meth_maybe_retry == NULL) { + if (!TEST_ptr(meth_maybe_retry = BIO_meth_new(BIO_TYPE_MAYBE_RETRY, + "Maybe Retry")) + || !TEST_true(BIO_meth_set_write(meth_maybe_retry, + maybe_retry_write)) + || !TEST_true(BIO_meth_set_ctrl(meth_maybe_retry, + maybe_retry_ctrl)) + || !TEST_true(BIO_meth_set_create(meth_maybe_retry, + maybe_retry_new)) + || !TEST_true(BIO_meth_set_destroy(meth_maybe_retry, + maybe_retry_free))) + return NULL; + } + return meth_maybe_retry; +} + +void bio_s_maybe_retry_free(void) +{ + BIO_meth_free(meth_maybe_retry); +} + +static int maybe_retry_new(BIO *bio) +{ + struct maybe_retry_data_st *data = OPENSSL_zalloc(sizeof(*data)); + + if (data == NULL) + return 0; + + BIO_set_data(bio, data); + BIO_set_init(bio, 1); + return 1; +} + +static int maybe_retry_free(BIO *bio) +{ + struct maybe_retry_data_st *data = BIO_get_data(bio); + + OPENSSL_free(data); + BIO_set_data(bio, NULL); + BIO_set_init(bio, 0); + return 1; +} + +static int maybe_retry_write(BIO *bio, const char *in, int inl) +{ + struct maybe_retry_data_st *data = BIO_get_data(bio); + + if (data == NULL) + return -1; + + if (data->retrycnt == 0) { + BIO_set_retry_write(bio); + return -1; + } + data->retrycnt--; + + return BIO_write(BIO_next(bio), in, inl); +} + +static long maybe_retry_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + struct maybe_retry_data_st *data = BIO_get_data(bio); + + if (data == NULL) + return 0; + + switch (cmd) { + case MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT: + data->retrycnt = num; + return 1; + + case BIO_CTRL_FLUSH: + if (data->retrycnt == 0) { + BIO_set_retry_write(bio); + return -1; + } + data->retrycnt--; + /* fall through */ + default: + return BIO_ctrl(BIO_next(bio), cmd, num, ptr); + } +} + int create_ssl_ctx_pair(OSSL_LIB_CTX *libctx, const SSL_METHOD *sm, const SSL_METHOD *cm, int min_proto_version, int max_proto_version, SSL_CTX **sctx, SSL_CTX **cctx, diff --git a/test/helpers/ssltestlib.h b/test/helpers/ssltestlib.h index c513769ddd9..eb54b04f2c6 100644 --- a/test/helpers/ssltestlib.h +++ b/test/helpers/ssltestlib.h @@ -44,6 +44,15 @@ const BIO_METHOD *bio_s_always_retry(void); void bio_s_always_retry_free(void); void set_always_retry_err_val(int err); +/* + * Maybe retry BIO ctrls. We make them large enough to not clash with standard + * BIO ctrl codes. + */ +#define MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT (1 << 15) + +const BIO_METHOD *bio_s_maybe_retry(void); +void bio_s_maybe_retry_free(void); + /* Packet types - value 0 is reserved */ #define INJECT_PACKET 1 #define INJECT_PACKET_IGNORE_REC_SEQ 2 diff --git a/test/sslapitest.c b/test/sslapitest.c index 9539b4cf3a6..2fbe76f9798 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -11138,6 +11138,102 @@ end: return testresult; } +/* + * Test that receiving retries when writing application data works as expected + */ +static int test_data_retry(void) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + int testresult = 0; + unsigned char inbuf[1200], outbuf[1200]; + size_t i; + BIO *tmp = NULL; + BIO *bretry = BIO_new(bio_s_maybe_retry()); + size_t written, readbytes, totread = 0; + + if (!TEST_ptr(bretry)) + goto end; + + for (i = 0; i < sizeof(inbuf); i++) + inbuf[i] = i; + memset(outbuf, 0, sizeof(outbuf)); + + if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(), + TLS_client_method(), 0, 0, &sctx, &cctx, + cert, privkey))) + goto end; + + if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL, + NULL))) + goto end; + + if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) + goto end; + + /* Smallest possible max send fragment is 512 */ + if (!TEST_true(SSL_set_max_send_fragment(clientssl, 512))) + goto end; + + tmp = SSL_get_wbio(clientssl); + if (!TEST_ptr(tmp)) + goto end; + if (!TEST_true(BIO_up_ref(tmp))) + goto end; + BIO_push(bretry, tmp); + tmp = NULL; + SSL_set0_wbio(clientssl, bretry); + if (!BIO_up_ref(bretry)) { + bretry = NULL; + goto end; + } + + for (i = 0; i < 3; i++) { + /* We expect this call to make no progress and indicate retry */ + if (!TEST_false(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written))) + goto end; + if (!TEST_int_eq(SSL_get_error(clientssl, 0), SSL_ERROR_WANT_WRITE)) + goto end; + + /* Allow one write to progess, but the next one to signal retry */ + if (!TEST_true(BIO_ctrl(bretry, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 1, + NULL))) + goto end; + + if (i == 2) + break; + + /* + * This call will hopefully make progress but will still indicate retry + * because there is more data than will fit into a single record. + */ + if (!TEST_false(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written))) + goto end; + if (!TEST_int_eq(SSL_get_error(clientssl, 0), SSL_ERROR_WANT_WRITE)) + goto end; + } + + /* The final call should write the last chunk of data and succeed */ + if (!TEST_true(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written))) + goto end; + /* Read all the data available */ + while (SSL_read_ex(serverssl, outbuf + totread, sizeof(outbuf) - totread, + &readbytes)) + totread += readbytes; + if (!TEST_mem_eq(inbuf, sizeof(inbuf), outbuf, totread)) + goto end; + + testresult = 1; +end: + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + BIO_free_all(bretry); + BIO_free(tmp); + return testresult; +} + OPT_TEST_DECLARE_USAGE("certfile privkeyfile srpvfile tmpfile provider config dhfile\n") int setup_tests(void) @@ -11444,6 +11540,7 @@ int setup_tests(void) ADD_ALL_TESTS(test_version, 6); ADD_TEST(test_rstate_string); ADD_ALL_TESTS(test_handshake_retry, 16); + ADD_TEST(test_data_retry); return 1; err: @@ -11473,6 +11570,7 @@ void cleanup_tests(void) OPENSSL_free(privkey8192); bio_s_mempacket_test_free(); bio_s_always_retry_free(); + bio_s_maybe_retry_free(); OSSL_PROVIDER_unload(defctxnull); OSSL_LIB_CTX_free(libctx); }