From 61e96557f9eae0258074c9cec7ad6aa1b9dde1df Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Thu, 3 May 2018 12:06:38 +0100 Subject: [PATCH] Add a DTLS test for dropped records Drop a record from a handshake and check that we can still complete the handshake. Repeat for all records in the handshake. Reviewed-by: Rich Salz (Merged from https://github.com/openssl/openssl/pull/6170) --- test/dtlstest.c | 121 +++++++++++++++++++++++++++++++++++++++++++++- test/ssltestlib.c | 83 ++++++++++++++++++++++++++++--- test/ssltestlib.h | 8 +++ 3 files changed, 205 insertions(+), 7 deletions(-) diff --git a/test/dtlstest.c b/test/dtlstest.c index 16d4e0f984..859ec6bee2 100644 --- a/test/dtlstest.c +++ b/test/dtlstest.c @@ -46,7 +46,7 @@ static unsigned int timer_cb(SSL *s, unsigned int timer_us) ++timer_cb_count; if (timer_us == 0) - return 1000000; + return 50000; else return 2 * timer_us; } @@ -114,6 +114,123 @@ static int test_dtls_unprocessed(int testidx) return testresult; } +#define CLI_TO_SRV_EPOCH_0_RECS 3 +#define CLI_TO_SRV_EPOCH_1_RECS 1 +#define SRV_TO_CLI_EPOCH_0_RECS 12 +#define SRV_TO_CLI_EPOCH_1_RECS 1 +#define TOTAL_FULL_HAND_RECORDS \ + (CLI_TO_SRV_EPOCH_0_RECS + CLI_TO_SRV_EPOCH_1_RECS + \ + SRV_TO_CLI_EPOCH_0_RECS + SRV_TO_CLI_EPOCH_1_RECS) + +#define CLI_TO_SRV_RESUME_EPOCH_0_RECS 3 +#define CLI_TO_SRV_RESUME_EPOCH_1_RECS 1 +#define SRV_TO_CLI_RESUME_EPOCH_0_RECS 2 +#define SRV_TO_CLI_RESUME_EPOCH_1_RECS 1 +#define TOTAL_RESUME_HAND_RECORDS \ + (CLI_TO_SRV_RESUME_EPOCH_0_RECS + CLI_TO_SRV_RESUME_EPOCH_1_RECS + \ + SRV_TO_CLI_RESUME_EPOCH_0_RECS + SRV_TO_CLI_RESUME_EPOCH_1_RECS) + +#define TOTAL_RECORDS (TOTAL_FULL_HAND_RECORDS + TOTAL_RESUME_HAND_RECORDS) + +static int test_dtls_drop_records(int idx) +{ + SSL_CTX *sctx = NULL, *cctx = NULL; + SSL *serverssl = NULL, *clientssl = NULL; + BIO *c_to_s_fbio, *mempackbio; + int testresult = 0; + int epoch = 0; + SSL_SESSION *sess = NULL; + int cli_to_srv_epoch0, cli_to_srv_epoch1, srv_to_cli_epoch0; + + if (!TEST_true(create_ssl_ctx_pair(DTLS_server_method(), + DTLS_client_method(), + DTLS1_VERSION, DTLS_MAX_VERSION, + &sctx, &cctx, cert, privkey))) + return 0; + + if (idx >= TOTAL_FULL_HAND_RECORDS) { + /* We're going to do a resumption handshake. Get a session first. */ + if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, NULL)) + || !TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE)) + || !TEST_ptr(sess = SSL_get1_session(clientssl))) + goto end; + + SSL_shutdown(clientssl); + SSL_shutdown(serverssl); + SSL_free(serverssl); + SSL_free(clientssl); + serverssl = clientssl = NULL; + + cli_to_srv_epoch0 = CLI_TO_SRV_RESUME_EPOCH_0_RECS; + cli_to_srv_epoch1 = CLI_TO_SRV_RESUME_EPOCH_1_RECS; + srv_to_cli_epoch0 = SRV_TO_CLI_RESUME_EPOCH_0_RECS; + idx -= TOTAL_FULL_HAND_RECORDS; + } else { + cli_to_srv_epoch0 = CLI_TO_SRV_EPOCH_0_RECS; + cli_to_srv_epoch1 = CLI_TO_SRV_EPOCH_1_RECS; + srv_to_cli_epoch0 = SRV_TO_CLI_EPOCH_0_RECS; + } + + c_to_s_fbio = BIO_new(bio_f_tls_dump_filter()); + if (!TEST_ptr(c_to_s_fbio)) + goto end; + + /* BIO is freed by create_ssl_connection on error */ + if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, c_to_s_fbio))) + goto end; + + if (sess != NULL) { + if (!TEST_true(SSL_set_session(clientssl, sess))) + goto end; + } + + DTLS_set_timer_cb(clientssl, timer_cb); + DTLS_set_timer_cb(serverssl, timer_cb); + + /* Work out which record to drop based on the test number */ + if (idx >= cli_to_srv_epoch0 + cli_to_srv_epoch1) { + mempackbio = SSL_get_wbio(serverssl); + idx -= cli_to_srv_epoch0 + cli_to_srv_epoch1; + if (idx >= srv_to_cli_epoch0) { + epoch = 1; + idx -= srv_to_cli_epoch0; + } + } else { + mempackbio = SSL_get_wbio(clientssl); + if (idx >= cli_to_srv_epoch0) { + epoch = 1; + idx -= cli_to_srv_epoch0; + } + mempackbio = BIO_next(mempackbio); + } + BIO_ctrl(mempackbio, MEMPACKET_CTRL_SET_DROP_EPOCH, epoch, NULL); + BIO_ctrl(mempackbio, MEMPACKET_CTRL_SET_DROP_REC, idx, NULL); + + if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE))) + goto end; + + if (sess != NULL && !TEST_true(SSL_session_reused(clientssl))) + goto end; + + /* If the test did what we planned then it should have dropped a record */ + if (!TEST_int_eq((int)BIO_ctrl(mempackbio, MEMPACKET_CTRL_GET_DROP_REC, 0, + NULL), -1)) + goto end; + + testresult = 1; + end: + SSL_SESSION_free(sess); + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} + int setup_tests(void) { if (!TEST_ptr(cert = test_get_argument(0)) @@ -121,6 +238,8 @@ int setup_tests(void) return 0; ADD_ALL_TESTS(test_dtls_unprocessed, NUM_TESTS); + ADD_ALL_TESTS(test_dtls_drop_records, TOTAL_RECORDS); + return 1; } diff --git a/test/ssltestlib.c b/test/ssltestlib.c index 959e3296a8..041ae26676 100644 --- a/test/ssltestlib.c +++ b/test/ssltestlib.c @@ -12,6 +12,34 @@ #include "internal/nelem.h" #include "ssltestlib.h" #include "testutil.h" +#include "e_os.h" + +#ifdef OPENSSL_SYS_UNIX +# include + +static ossl_inline void ossl_sleep(unsigned int millis) { + usleep(millis * 1000); +} +#elif defined(_WIN32) +# include + +static ossl_inline void ossl_sleep(unsigned int millis) { + Sleep(millis); +} +#else +/* Fallback to a busy wait */ +static ossl_inline void ossl_sleep(unsigned int millis) { + struct timeval start, now; + unsigned int elapsedms; + + gettimeofday(&start, NULL); + do { + gettimeofday(&now, NULL); + elapsedms = (((now.tv_sec - start.tv_sec) * 1000000) + + now.tv_usec - start.tv_usec) / 1000; + } while (elapsedms < millis); +} +#endif static int tls_dump_new(BIO *bi); static int tls_dump_free(BIO *a); @@ -252,7 +280,10 @@ typedef struct mempacket_test_ctx_st { unsigned int currrec; unsigned int currpkt; unsigned int lastpkt; + unsigned int injected; unsigned int noinject; + unsigned int dropepoch; + int droprec; } MEMPACKET_TEST_CTX; static int mempacket_test_new(BIO *bi); @@ -295,6 +326,8 @@ static int mempacket_test_new(BIO *bio) OPENSSL_free(ctx); return 0; } + ctx->dropepoch = 0; + ctx->droprec = -1; BIO_set_init(bio, 1); BIO_set_data(bio, ctx); return 1; @@ -312,8 +345,8 @@ static int mempacket_test_free(BIO *bio) } /* Record Header values */ -#define EPOCH_HI 4 -#define EPOCH_LO 5 +#define EPOCH_HI 3 +#define EPOCH_LO 4 #define RECORD_SEQUENCE 10 #define RECORD_LEN_HI 11 #define RECORD_LEN_LO 12 @@ -341,15 +374,15 @@ static int mempacket_test_read(BIO *bio, char *out, int outl) if (outl > thispkt->len) outl = thispkt->len; - if (thispkt->type != INJECT_PACKET_IGNORE_REC_SEQ) { + if (thispkt->type != INJECT_PACKET_IGNORE_REC_SEQ + && (ctx->injected || ctx->droprec >= 0)) { /* * Overwrite the record sequence number. We strictly number them in * the order received. Since we are actually a reliable transport * we know that there won't be any re-ordering. We overwrite to deal * with any packets that have been injected */ - for (rem = thispkt->len, rec = thispkt->data - ; rem > 0; rec += len, rem -= len) { + for (rem = thispkt->len, rec = thispkt->data; rem > 0; rem -= len) { if (rem < DTLS1_RT_HEADER_LENGTH) return -1; epoch = (rec[EPOCH_HI] << 8) | rec[EPOCH_LO]; @@ -364,10 +397,23 @@ static int mempacket_test_read(BIO *bio, char *out, int outl) seq >>= 8; offset++; } while (seq > 0); - ctx->currrec++; len = ((rec[RECORD_LEN_HI] << 8) | rec[RECORD_LEN_LO]) + DTLS1_RT_HEADER_LENGTH; + if (rem < (int)len) + return -1; + if (ctx->droprec == (int)ctx->currrec && ctx->dropepoch == epoch) { + if (rem > (int)len) + memmove(rec, rec + len, rem - len); + outl -= len; + ctx->droprec = -1; + if (outl == 0) + BIO_set_retry_read(bio); + } else { + rec += len; + } + + ctx->currrec++; } } @@ -390,6 +436,7 @@ int mempacket_test_inject(BIO *bio, const char *in, int inl, int pktnum, if (pktnum >= 0) { if (ctx->noinject) return -1; + ctx->injected = 1; } else { ctx->noinject = 1; } @@ -488,6 +535,15 @@ static long mempacket_test_ctrl(BIO *bio, int cmd, long num, void *ptr) case BIO_CTRL_FLUSH: ret = 1; break; + case MEMPACKET_CTRL_SET_DROP_EPOCH: + ctx->dropepoch = (unsigned int)num; + break; + case MEMPACKET_CTRL_SET_DROP_REC: + ctx->droprec = (int)num; + break; + case MEMPACKET_CTRL_GET_DROP_REC: + ret = ctx->droprec; + break; case BIO_CTRL_RESET: case BIO_CTRL_DUP: case BIO_CTRL_PUSH: @@ -627,6 +683,7 @@ int create_ssl_connection(SSL *serverssl, SSL *clientssl, int want) int clienterr = 0, servererr = 0; unsigned char buf; size_t readbytes; + int isdtls = SSL_is_dtls(serverssl); do { err = SSL_ERROR_WANT_WRITE; @@ -658,10 +715,24 @@ int create_ssl_connection(SSL *serverssl, SSL *clientssl, int want) return 0; if (clienterr && servererr) return 0; + if (isdtls) { + if (rets > 0 && retc <= 0) + DTLSv1_handle_timeout(serverssl); + if (retc > 0 && rets <= 0) + DTLSv1_handle_timeout(clientssl); + } if (++abortctr == MAXLOOPS) { TEST_info("No progress made"); return 0; } + if (isdtls && abortctr <= 50 && (abortctr % 10) == 0) { + /* + * It looks like we're just spinning. Pause for a short period to + * give the DTLS timer a chance to do something. We only do this for + * the first few times to prevent hangs. + */ + ossl_sleep(50); + } } while (retc <=0 || rets <= 0); /* diff --git a/test/ssltestlib.h b/test/ssltestlib.h index 353699dec9..c96dff5216 100644 --- a/test/ssltestlib.h +++ b/test/ssltestlib.h @@ -32,6 +32,14 @@ void bio_s_mempacket_test_free(void); #define INJECT_PACKET 1 #define INJECT_PACKET_IGNORE_REC_SEQ 2 +/* + * Mempacket BIO ctrls. We make them large enough to not clash with standard BIO + * ctrl codes. + */ +#define MEMPACKET_CTRL_SET_DROP_EPOCH (1 << 15) +#define MEMPACKET_CTRL_SET_DROP_REC (2 << 15) +#define MEMPACKET_CTRL_GET_DROP_REC (3 << 15) + int mempacket_test_inject(BIO *bio, const char *in, int inl, int pktnum, int type);