From: Matt Caswell Date: Wed, 30 Nov 2022 14:21:00 +0000 (+0000) Subject: Add a skeleton quicfaultstest X-Git-Tag: openssl-3.2.0-alpha1~1264 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=adef87a2c6a0136aa3d965162932f961daf28411;p=thirdparty%2Fopenssl.git Add a skeleton quicfaultstest Also includes helper support to create a QUIC connection inside a test. We wil use quicfaultstest to deliberately inject faulty datagrams/packets to test how we handle them. Reviewed-by: Hugo Landau Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/20030) --- diff --git a/include/internal/quic_tserver.h b/include/internal/quic_tserver.h index a19ec882ef7..0d564d8c21b 100644 --- a/include/internal/quic_tserver.h +++ b/include/internal/quic_tserver.h @@ -53,6 +53,9 @@ int ossl_quic_tserver_tick(QUIC_TSERVER *srv); /* Returns 1 if we have a (non-terminated) client. */ int ossl_quic_tserver_is_connected(QUIC_TSERVER *srv); +/* Returns 1 if the server is in any terminating or terminated state */ +int ossl_quic_tserver_is_term_any(QUIC_TSERVER *srv); + /* * Attempts to read from stream 0. Writes the number of bytes read to * *bytes_read and returns 1 on success. If no bytes are available, 0 is written diff --git a/ssl/quic/quic_tserver.c b/ssl/quic/quic_tserver.c index 2b5f04ac5a9..ea131ca9c15 100644 --- a/ssl/quic/quic_tserver.c +++ b/ssl/quic/quic_tserver.c @@ -147,6 +147,12 @@ int ossl_quic_tserver_is_connected(QUIC_TSERVER *srv) return ossl_quic_channel_is_active(srv->ch); } +/* Returns 1 if the server is in any terminating or terminated state */ +int ossl_quic_tserver_is_term_any(QUIC_TSERVER *srv) +{ + return ossl_quic_channel_is_term_any(srv->ch); +} + int ossl_quic_tserver_read(QUIC_TSERVER *srv, unsigned char *buf, size_t buf_len, diff --git a/test/build.info b/test/build.info index 3b8d17a3251..15c0985150f 100644 --- a/test/build.info +++ b/test/build.info @@ -71,7 +71,7 @@ IF[{- !$disabled{tests} -}] ENDIF IF[{- !$disabled{quic} -}] - PROGRAMS{noinst}=priority_queue_test event_queue_test + PROGRAMS{noinst}=priority_queue_test event_queue_test quicfaultstest ENDIF IF[{- !$disabled{comp} && (!$disabled{brotli} || !$disabled{zstd} || !$disabled{zlib}) -}] @@ -798,6 +798,10 @@ IF[{- !$disabled{tests} -}] SOURCE[event_queue_test]=event_queue_test.c INCLUDE[event_queue_test]=../include ../apps/include DEPEND[event_queue_test]=../libcrypto ../libssl.a libtestutil.a + + SOURCE[quicfaultstest]=quicfaultstest.c helpers/quictestlib.c + INCLUDE[quicfaultstest]=../include ../apps/include .. + DEPEND[quicfaultstest]=../libcrypto.a ../libssl.a libtestutil.a ENDIF SOURCE[dhtest]=dhtest.c diff --git a/test/helpers/quictestlib.c b/test/helpers/quictestlib.c new file mode 100644 index 00000000000..1c4fa8d9b33 --- /dev/null +++ b/test/helpers/quictestlib.c @@ -0,0 +1,149 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include "quictestlib.h" +#include "../testutil.h" + +struct ossl_quic_fault { + QUIC_TSERVER *qtserv; +}; + +int qtest_create_quic_objects(SSL_CTX *clientctx, char *certfile, char *keyfile, + QUIC_TSERVER **qtserv, SSL **cssl, + OSSL_QUIC_FAULT **fault) +{ + /* ALPN value as recognised by QUIC_TSERVER */ + unsigned char alpn[] = { 8, 'o', 's', 's', 'l', 't', 'e', 's', 't' }; + QUIC_TSERVER_ARGS tserver_args = {0}; + BIO *bio1 = NULL, *bio2 = NULL; + BIO_ADDR *peeraddr = NULL; + struct in_addr ina = {0}; + + *qtserv = NULL; + if (fault != NULL) + *fault = NULL; + *cssl = SSL_new(clientctx); + if (!TEST_ptr(*cssl)) + return 0; + + if (!TEST_true(SSL_set_blocking_mode(*cssl, 0))) + goto err; + + /* SSL_set_alpn_protos returns 0 for success! */ + if (!TEST_false(SSL_set_alpn_protos(*cssl, alpn, sizeof(alpn)))) + goto err; + + if (!TEST_true(BIO_new_bio_dgram_pair(&bio1, 0, &bio2, 0))) + goto err; + + if (!TEST_true(BIO_dgram_set_caps(bio1, BIO_DGRAM_CAP_HANDLES_DST_ADDR)) + || !TEST_true(BIO_dgram_set_caps(bio2, BIO_DGRAM_CAP_HANDLES_DST_ADDR))) + goto err; + + SSL_set_bio(*cssl, bio1, bio1); + + if (!TEST_ptr(peeraddr = BIO_ADDR_new())) + goto err; + + /* Dummy server address */ + if (!TEST_true(BIO_ADDR_rawmake(peeraddr, AF_INET, &ina, sizeof(ina), + htons(0)))) + goto err; + + if (!TEST_true(SSL_set_initial_peer_addr(*cssl, peeraddr))) + goto err; + + /* 2 refs are passed for bio2 */ + if (!BIO_up_ref(bio2)) + goto err; + tserver_args.net_rbio = bio2; + tserver_args.net_wbio = bio2; + + if (!TEST_ptr(*qtserv = ossl_quic_tserver_new(&tserver_args, certfile, + keyfile))) { + /* We hold 2 refs to bio2 at the moment */ + BIO_free(bio2); + goto err; + } + /* Ownership of bio2 is now held by *qtserv */ + bio2 = NULL; + + if (fault != NULL) { + *fault = OPENSSL_zalloc(sizeof(**fault)); + if (*fault == NULL) + goto err; + + (*fault)->qtserv = *qtserv; + } + + BIO_ADDR_free(peeraddr); + + return 1; + err: + BIO_ADDR_free(peeraddr); + BIO_free(bio1); + BIO_free(bio2); + SSL_free(*cssl); + ossl_quic_tserver_free(*qtserv); + if (fault != NULL) + OPENSSL_free(*fault); + + return 0; +} + +#define MAXLOOPS 1000 + +int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl) +{ + int retc = -1, rets = 0, err, abortctr = 0, ret = 0; + int clienterr = 0, servererr = 0; + + do { + err = SSL_ERROR_WANT_WRITE; + while (!clienterr && retc <= 0 && err == SSL_ERROR_WANT_WRITE) { + retc = SSL_connect(clientssl); + if (retc <= 0) + err = SSL_get_error(clientssl, retc); + } + + if (!clienterr && retc <= 0 && err != SSL_ERROR_WANT_READ) { + TEST_info("SSL_connect() failed %d, %d", retc, err); + TEST_openssl_errors(); + clienterr = 1; + } + + /* + * We're cheating. We don't take any notice of SSL_get_tick_timeout() + * and tick everytime around the loop anyway. This is inefficient. We + * can get away with it in test code because we control both ends of + * the communications and don't expect network delays. This shouldn't + * be done in a real application. + */ + if (!clienterr) + SSL_tick(clientssl); + if (!servererr) { + ossl_quic_tserver_tick(qtserv); + servererr = ossl_quic_tserver_is_term_any(qtserv); + if (!servererr && !rets) + rets = ossl_quic_tserver_is_connected(qtserv); + } + + if (clienterr && servererr) + goto err; + + if (++abortctr == MAXLOOPS) { + TEST_info("No progress made"); + goto err; + } + } while (retc <=0 || rets <= 0); + + ret = 1; + err: + return ret; +} diff --git a/test/helpers/quictestlib.h b/test/helpers/quictestlib.h new file mode 100644 index 00000000000..3afea60e558 --- /dev/null +++ b/test/helpers/quictestlib.h @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include + +typedef struct ossl_quic_fault OSSL_QUIC_FAULT; + +int qtest_create_quic_objects(SSL_CTX *clientctx, char *certfile, char *keyfile, + QUIC_TSERVER **qtserv, SSL **cssl, + OSSL_QUIC_FAULT **fault); +int qtest_create_quic_connection(QUIC_TSERVER *qtserv, SSL *clientssl); diff --git a/test/quicfaultstest.c b/test/quicfaultstest.c new file mode 100644 index 00000000000..30d7caf9ac1 --- /dev/null +++ b/test/quicfaultstest.c @@ -0,0 +1,103 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include +#include "helpers/quictestlib.h" +#include "testutil.h" + +static char *cert = NULL; +static char *privkey = NULL; + +/* + * Basic test that just creates a connection and sends some data without any + * faults injected. + */ +static int test_basic(void) +{ + int testresult = 0; + SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method()); + QUIC_TSERVER *qtserv = NULL; + SSL *cssl = NULL; + char *msg = "Hello World!"; + size_t msglen = strlen(msg); + unsigned char buf[80]; + size_t bytesread; + + if (!TEST_ptr(cctx)) + goto err; + + if (!TEST_true(qtest_create_quic_objects(cctx, cert, privkey, &qtserv, + &cssl, NULL))) + goto err; + + if (!TEST_true(qtest_create_quic_connection(qtserv, cssl))) + goto err; + + if (!TEST_int_eq(SSL_write(cssl, msg, msglen), msglen)) + goto err; + + ossl_quic_tserver_tick(qtserv); + if (!TEST_true(ossl_quic_tserver_read(qtserv, buf, sizeof(buf), &bytesread))) + goto err; + + /* + * We assume the entire message is read from the server in one go. In + * theory this could get fragmented but its a small message so we assume + * not. + */ + if (!TEST_mem_eq(msg, msglen, buf, bytesread)) + goto err; + + testresult = 1; + err: + SSL_free(cssl); + ossl_quic_tserver_free(qtserv); + SSL_CTX_free(cctx); + return testresult; +} + +OPT_TEST_DECLARE_USAGE("certsdir\n") + +int setup_tests(void) +{ + char *certsdir = NULL; + + if (!test_skip_common_options()) { + TEST_error("Error parsing test options\n"); + return 0; + } + + if (!TEST_ptr(certsdir = test_get_argument(0))) + return 0; + + + cert = test_mk_file_path(certsdir, "servercert.pem"); + if (cert == NULL) + goto err; + + privkey = test_mk_file_path(certsdir, "serverkey.pem"); + if (privkey == NULL) + goto err; + + ADD_TEST(test_basic); + + return 1; + + err: + OPENSSL_free(cert); + OPENSSL_free(privkey); + return 0; +} + +void cleanup_tests(void) +{ + OPENSSL_free(cert); + OPENSSL_free(privkey); +} diff --git a/test/recipes/90-test_quicfaults.t b/test/recipes/90-test_quicfaults.t new file mode 100644 index 00000000000..f4bd8ea9b76 --- /dev/null +++ b/test/recipes/90-test_quicfaults.t @@ -0,0 +1,26 @@ +#! /usr/bin/env perl +# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +use OpenSSL::Test::Utils; +use OpenSSL::Test qw/:DEFAULT srctop_dir bldtop_dir/; + +BEGIN { +setup("test_quicfaults"); +} + +use lib srctop_dir('Configurations'); +use lib bldtop_dir('.'); + +plan skip_all => "QUIC protocol is not supported by this OpenSSL build" + if disabled('quic'); + +plan tests => 1; + +ok(run(test(["quicfaultstest", srctop_dir("test", "certs")])), + "running quicfaultstest");