From 54bd786707596dff8f32fde505b46fa9e2457cce Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 12 Aug 2022 12:23:06 +0200 Subject: [PATCH] DTLS limit fragments - Limit the amount of fragments we store per a DTLS connection - Error out when we reach the DTLS fragment connection limit --- src/dtls13.c | 51 ++++++++---- src/internal.c | 44 +++++++--- src/ssl.c | 4 +- src/tls13.c | 4 +- tests/api.c | 199 ++++++++++++++++++++++++++++++++++++++++++++ wolfssl/error-ssl.h | 2 +- wolfssl/internal.h | 7 +- wolfssl/test.h | 34 ++++---- 8 files changed, 296 insertions(+), 49 deletions(-) diff --git a/src/dtls13.c b/src/dtls13.c index 1b248b466..7cb02bd28 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -1514,17 +1514,13 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, if (ret != 0) return PARSE_ERROR; - if (idx + fragLength > size) { - WOLFSSL_ERROR(INCOMPLETE_DATA); - return INCOMPLETE_DATA; - } - - if (fragOff + fragLength > messageLength) - return BUFFER_ERROR; - - if (handshakeType == client_hello && - /* Only when receiving an unverified ClientHello */ - ssl->options.serverState < SERVER_HELLO_COMPLETE) { + if (ssl->options.side == WOLFSSL_SERVER_END && + ssl->options.acceptState < TLS13_ACCEPT_FIRST_REPLY_DONE) { + if (handshakeType != client_hello) { + WOLFSSL_MSG("Ignoring other messages before we verify a ClientHello"); + *processedSize = size; + return 0; + } /* To be able to operate in stateless mode, we assume the ClientHello * is in order and we use its Handshake Message number and Sequence * Number for our Tx. */ @@ -1534,6 +1530,14 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, ssl->dtls13Epochs[0].nextSeqNumber = ssl->keys.curSeq; } + if (idx + fragLength > size) { + WOLFSSL_ERROR(INCOMPLETE_DATA); + return INCOMPLETE_DATA; + } + + if (fragOff + fragLength > messageLength) + return BUFFER_ERROR; + ret = Dtls13RtxMsgRecvd(ssl, (enum HandShakeType)handshakeType, fragOff); if (ret != 0) return ret; @@ -1554,6 +1558,16 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, isFirst = fragOff == 0; isComplete = isFirst && fragLength == messageLength; + + if (!isComplete && !IsEncryptionOn(ssl, 0)) { +#ifdef WOLFSSL_DEBUG_TLS + WOLFSSL_MSG("DTLS1.3 not accepting fragmented plaintext message"); +#endif /* WOLFSSL_DEBUG_TLS */ + /* ignore the message */ + *processedSize = idx + fragLength + ssl->keys.padSz; + return 0; + } + usingAsyncCrypto = ssl->devId != INVALID_DEVID; /* store the message if any of the following: (a) incomplete message, (b) @@ -1565,10 +1579,17 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, ssl->keys.dtls_peer_handshake_number > ssl->keys.dtls_expected_peer_handshake_number || usingAsyncCrypto) { - DtlsMsgStore(ssl, w64GetLow32(ssl->keys.curEpoch64), - ssl->keys.dtls_peer_handshake_number, - input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, - fragOff, fragLength, ssl->heap); + if (ssl->dtls_rx_msg_list_sz < DTLS_POOL_SZ) { + DtlsMsgStore(ssl, w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, ssl->heap); + } + else { + /* DTLS_POOL_SZ outstanding messages is way more than enough for any + * valid peer */ + return DTLS_TOO_MANY_FRAGMENTS_E; + } *processedSize = idx + fragLength + ssl->keys.padSz; if (Dtls13NextMessageComplete(ssl)) diff --git a/src/internal.c b/src/internal.c index 7098d3380..25653ee89 100644 --- a/src/internal.c +++ b/src/internal.c @@ -522,7 +522,7 @@ int IsAtLeastTLSv1_3(const ProtocolVersion pv) return ret; } -static WC_INLINE int IsEncryptionOn(WOLFSSL* ssl, int isSend) +int IsEncryptionOn(WOLFSSL* ssl, int isSend) { #ifdef WOLFSSL_DTLS /* For DTLS, epoch 0 is always not encrypted. */ @@ -16177,6 +16177,16 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, } } +#if !defined(NO_WOLFSSL_SERVER) + if (ssl->options.side == WOLFSSL_SERVER_END && + ssl->options.acceptState < ACCEPT_FIRST_REPLY_DONE && + type != client_hello) { + WOLFSSL_MSG("Ignoring other messages before we verify a ClientHello"); + *inOutIdx = totalSz; + return 0; + } +#endif + /* Check the handshake sequence number first. If out of order, * add the current message to the list. If the message is in order, * but it is a fragment, add the current message to the list, then @@ -16204,12 +16214,15 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, * with newer and newer cookies.) */ if (type != client_hello) { WOLFSSL_MSG("Current message is out of order"); - if (ssl->dtls_rx_msg_list_sz < DTLS_POOL_SZ) { - DtlsMsgStore(ssl, ssl->keys.curEpoch, - ssl->keys.dtls_peer_handshake_number, - input + *inOutIdx, size, type, - fragOffset, fragSz, ssl->heap); + if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { + WOLFSSL_MSG("Reached rx msg limit error"); + return DTLS_TOO_MANY_FRAGMENTS_E; } + + DtlsMsgStore(ssl, ssl->keys.curEpoch, + ssl->keys.dtls_peer_handshake_number, + input + *inOutIdx, size, type, + fragOffset, fragSz, ssl->heap); *inOutIdx += fragSz; #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) if (ssl->options.startedETMRead && ssl->keys.curEpoch != 0) { @@ -16305,12 +16318,15 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, return 0; } - if (ssl->dtls_rx_msg_list_sz < DTLS_POOL_SZ) { - DtlsMsgStore(ssl, ssl->keys.curEpoch, - ssl->keys.dtls_peer_handshake_number, - input + *inOutIdx, size, type, - fragOffset, fragSz, ssl->heap); + if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { + WOLFSSL_MSG("Reached rx msg limit error"); + WOLFSSL_ERROR(DTLS_TOO_MANY_FRAGMENTS_E); + return DTLS_TOO_MANY_FRAGMENTS_E; } + DtlsMsgStore(ssl, ssl->keys.curEpoch, + ssl->keys.dtls_peer_handshake_number, + input + *inOutIdx, size, type, + fragOffset, fragSz, ssl->heap); *inOutIdx += fragSz; *inOutIdx += ssl->keys.padSz; #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) @@ -16354,6 +16370,10 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, /* In async mode always store the message and process it with * DtlsMsgDrain because in case of a WC_PENDING_E it will be * easier this way. */ + if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { + WOLFSSL_MSG("Reached rx msg limit error"); + return DTLS_TOO_MANY_FRAGMENTS_E; + } DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + idx, size, type, @@ -22862,6 +22882,8 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) #endif case DTLS_CID_ERROR: return "DTLS ConnectionID mismatch or missing"; + case DTLS_TOO_MANY_FRAGMENTS_E: + return "Received too many fragmented messages from peer error"; default : return "unknown error number"; diff --git a/src/ssl.c b/src/ssl.c index 70edba2b8..08f78e32b 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -12406,8 +12406,6 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, FALL_THROUGH; case HELLO_AGAIN : - if (ssl->options.certOnly) - return WOLFSSL_SUCCESS; #ifdef WOLFSSL_TLS13 if (ssl->options.tls1_3) @@ -12461,6 +12459,8 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, FALL_THROUGH; case FIRST_REPLY_DONE : + if (ssl->options.certOnly) + return WOLFSSL_SUCCESS; #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CLIENT_AUTH) #ifdef WOLFSSL_TLS13 if (ssl->options.tls1_3) diff --git a/src/tls13.c b/src/tls13.c index be17481c4..db29344e0 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -10354,8 +10354,6 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) FALL_THROUGH; case HELLO_AGAIN: - if (ssl->options.certOnly) - return WOLFSSL_SUCCESS; if (ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE) { @@ -10403,6 +10401,8 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) FALL_THROUGH; case FIRST_REPLY_DONE: + if (ssl->options.certOnly) + return WOLFSSL_SUCCESS; #ifdef WOLFSSL_EARLY_DATA if (!ssl->options.dtls && ssl->earlyData != no_early_data && !WOLFSSL_IS_QUIC(ssl)) { diff --git a/tests/api.c b/tests/api.c index b8e4ef651..c0b0cdf21 100644 --- a/tests/api.c +++ b/tests/api.c @@ -5038,6 +5038,9 @@ static THREAD_RETURN WOLFSSL_THREAD test_server_nofail(void* args) opts->return_code = TEST_SUCCESS; done: + if (cbf != NULL) + cbf->last_err = err; + wolfSSL_shutdown(ssl); wolfSSL_free(ssl); if (!sharedCtx) @@ -5467,6 +5470,9 @@ static int test_client_nofail(void* args, cbType cb) ((func_args*)args)->return_code = TEST_SUCCESS; done: + if (cbf != NULL) + cbf->last_err = err; + wolfSSL_free(ssl); if (!sharedCtx) wolfSSL_CTX_free(ctx); @@ -55001,6 +55007,198 @@ static int test_wolfSSL_dtls_plaintext(void) { } #endif +#if defined(HAVE_IO_TESTS_DEPENDENCIES) && !defined(SINGLE_THREADED) && \ + defined(WOLFSSL_DTLS) + +static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) +{ + byte b[1100]; /* buffer for the messages to send */ + size_t idx = 0; + size_t seq_offset = 0; + size_t msg_offset = 0; + int i; + int fd = wolfSSL_get_fd(ssl); + int ret = wolfSSL_connect_cert(ssl); /* This gets us past the cookie */ + word32 seq_number = 100; /* start high so server definitely reads this */ + word16 msg_number = 50; /* start high so server has to buffer this */ + AssertIntEQ(ret, 1); + /* Now let's start spamming the peer with fragments it needs to store */ + XMEMSET(b, -1, sizeof(b)); + + /* record layer */ + /* handshake type */ + b[idx++] = 22; + /* protocol version */ + b[idx++] = 0xfe; + b[idx++] = 0xfd; /* DTLS 1.2 */ + /* epoch 0 */ + XMEMSET(b + idx, 0, 2); + idx += 2; + /* sequence number */ + XMEMSET(b + idx, 0, 6); + seq_offset = idx + 2; /* increment only the low 32 bits */ + idx += 6; + /* static length in BE */ + c16toa(42, b + idx); + idx += 2; + + /* handshake layer */ + /* cert type */ + b[idx++] = 11; + /* length */ + c32to24(1000, b + idx); + idx += 3; + /* message seq */ + c16toa(0, b + idx); + msg_offset = idx; + idx += 2; + /* frag offset */ + c32to24(500, b + idx); + idx += 3; + /* frag length */ + c32to24(30, b + idx); + idx += 3; + + for (i = 0; i < DTLS_POOL_SZ * 2 && ret > 0; + seq_number++, msg_number++, i++) { + struct timespec delay; + XMEMSET(&delay, 0, sizeof(delay)); + delay.tv_nsec = 10000000; /* wait 0.01 seconds */ + c32toa(seq_number, b + seq_offset); + c16toa(msg_number, b + msg_offset); + ret = (int)send(fd, b, 55, 0); + nanosleep(&delay, NULL); + } +} + +#ifdef WOLFSSL_DTLS13 +static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) +{ + byte b[150]; /* buffer for the messages to send */ + size_t idx = 0; + size_t msg_offset = 0; + int fd = wolfSSL_get_fd(ssl); + word16 msg_number = 10; /* start high so server has to buffer this */ + int ret = wolfSSL_connect_cert(ssl); /* This gets us past the cookie */ + AssertIntEQ(ret, 1); + /* Now let's start spamming the peer with fragments it needs to store */ + XMEMSET(b, -1, sizeof(b)); + + /* handshake type */ + b[idx++] = 11; + /* length */ + c32to24(10000, b + idx); + idx += 3; + /* message_seq */ + msg_offset = idx; + idx += 2; + /* fragment_offset */ + c32to24(5000, b + idx); + idx += 3; + /* fragment_length */ + c32to24(100, b + idx); + idx += 3; + /* fragment contents */ + idx += 100; + + for (; ret > 0; msg_number++) { + byte sendBuf[150]; + int sendSz = sizeof(sendBuf); + struct timespec delay; + XMEMSET(&delay, 0, sizeof(delay)); + delay.tv_nsec = 10000000; /* wait 0.01 seconds */ + c16toa(msg_number, b + msg_offset); + sendSz = BuildTls13Message(ssl, sendBuf, sendSz, b, + (int)idx, handshake, 0, 0, 0); + ret = (int)send(fd, sendBuf, (size_t)sendSz, 0); + nanosleep(&delay, NULL); + } +} +#endif + +static int test_wolfSSL_dtls_fragments(void) +{ + tcp_ready ready; + func_args client_args; + func_args server_args; + callback_functions func_cb_client; + callback_functions func_cb_server; + THREAD_TYPE serverThread; + size_t i; + struct test_params { + method_provider client_meth; + method_provider server_meth; + ssl_callback spammer; + } params[] = { + {wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, + test_wolfSSL_dtls12_fragments_spammer}, +#ifdef WOLFSSL_DTLS13 + {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, + test_wolfSSL_dtls13_fragments_spammer}, +#endif + }; + + printf(testingFmt, "test_wolfSSL_dtls_fragments"); + + for (i = 0; i < sizeof(params)/sizeof(*params); i++) { + XMEMSET(&client_args, 0, sizeof(func_args)); + XMEMSET(&server_args, 0, sizeof(func_args)); + XMEMSET(&func_cb_client, 0, sizeof(callback_functions)); + XMEMSET(&func_cb_server, 0, sizeof(callback_functions)); + + #ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); + #endif + + StartTCP(); + InitTcpReady(&ready); + +#if defined(USE_WINDOWS_API) + /* use RNG to get random port if using windows */ + ready.port = GetRandomPort(); +#endif + + server_args.signal = &ready; + server_args.callbacks = &func_cb_server; + client_args.signal = &ready; + client_args.callbacks = &func_cb_client; + + func_cb_client.doUdp = func_cb_server.doUdp = 1; + func_cb_server.method = params[i].server_meth; + func_cb_client.method = params[i].client_meth; + func_cb_client.ssl_ready = params[i].spammer; + + start_thread(test_server_nofail, &server_args, &serverThread); + wait_tcp_ready(&server_args); + test_client_nofail(&client_args, NULL); + join_thread(serverThread); + + AssertFalse(client_args.return_code); + AssertFalse(server_args.return_code); + /* The socket should be closed by the server resulting in a + * socket error */ + AssertIntEQ(func_cb_client.last_err, SOCKET_ERROR_E); + /* Check the server returned an error indicating the msg buffer + * was full */ + AssertIntEQ(func_cb_server.last_err, DTLS_TOO_MANY_FRAGMENTS_E); + + FreeTcpReady(&ready); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + } + + printf(resultFmt, passed); + + return 0; +} +#else +static int test_wolfSSL_dtls_fragments(void) { + return 0; +} +#endif + #if !defined(NO_RSA) && !defined(NO_SHA) && !defined(NO_FILESYSTEM) && \ !defined(NO_CERTS) && (!defined(NO_WOLFSSL_CLIENT) || \ !defined(WOLFSSL_NO_CLIENT_AUTH)) @@ -57576,6 +57774,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_msgCb), TEST_DECL(test_wolfSSL_either_side), TEST_DECL(test_wolfSSL_DTLS_either_side), + TEST_DECL(test_wolfSSL_dtls_fragments), TEST_DECL(test_generate_cookie), TEST_DECL(test_wolfSSL_X509_STORE_set_flags), TEST_DECL(test_wolfSSL_X509_LOOKUP_load_file), diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 319037966..754191403 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -178,8 +178,8 @@ enum wolfSSL_ErrorCodes { FALCON_KEY_SIZE_E = -451, /* Wrong key size for Falcon. */ QUIC_TP_MISSING_E = -452, /* QUIC transport parameter missing */ DILITHIUM_KEY_SIZE_E = -453, /* Wrong key size for Dilithium. */ - DTLS_CID_ERROR = -454, /* Wrong or missing CID */ + DTLS_TOO_MANY_FRAGMENTS_E = -455, /* Received too many fragments */ /* add strings to wolfSSL_ERR_reason_error_string in internal.c !!!!! */ /* begin negotiation parameter errors */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 0015c19d1..1470c4f50 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1383,7 +1383,8 @@ enum Misc { DTLS_RECORD_EXTRA = 8, /* diff from normal */ DTLS_HANDSHAKE_SEQ_SZ = 2, /* handshake header sequence number */ DTLS_HANDSHAKE_FRAG_SZ = 3, /* fragment offset and length are 24 bit */ - DTLS_POOL_SZ = 255,/* allowed number of list items in TX pool */ + DTLS_POOL_SZ = 20, /* allowed number of list items in TX and + * RX pool */ DTLS_EXPORT_PRO = 165,/* wolfSSL protocol for serialized session */ DTLS_EXPORT_STATE_PRO = 166,/* wolfSSL protocol for serialized state */ TLS_EXPORT_PRO = 167,/* wolfSSL protocol for serialized TLS */ @@ -5312,6 +5313,7 @@ WOLFSSL_LOCAL int StoreKeys(WOLFSSL* ssl, const byte* keyData, int side); WOLFSSL_LOCAL int IsTLS(const WOLFSSL* ssl); WOLFSSL_LOCAL int IsAtLeastTLSv1_2(const WOLFSSL* ssl); WOLFSSL_LOCAL int IsAtLeastTLSv1_3(ProtocolVersion pv); +WOLFSSL_LOCAL int IsEncryptionOn(WOLFSSL* ssl, int isSend); WOLFSSL_LOCAL int TLSv1_3_Capable(WOLFSSL* ssl); WOLFSSL_LOCAL void FreeHandshakeResources(WOLFSSL* ssl); @@ -5608,7 +5610,8 @@ WOLFSSL_LOCAL int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, int sizeOnly, int asyncOkay, int epochOrder); #ifdef WOLFSSL_TLS13 -int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, +/* Use WOLFSSL_API to use this function in tests/api.c */ +WOLFSSL_API int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, int inSz, int type, int hashOutput, int sizeOnly, int asyncOkay); #endif diff --git a/wolfssl/test.h b/wolfssl/test.h index 12d34650f..ec4569973 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -613,6 +613,7 @@ typedef struct callback_functions { #endif int devId; int return_code; + int last_err; unsigned char isSharedCtx:1; unsigned char loadToSSL:1; unsigned char ticNoInit:1; @@ -2093,32 +2094,33 @@ static WC_INLINE void udp_accept(SOCKET_T* sockfd, SOCKET_T* clientfd, } #endif + if (args != NULL && args->signal != NULL) { #if defined(_POSIX_THREADS) && !defined(__MINGW32__) - /* signal ready to accept data */ - { - tcp_ready* ready = args->signal; - PTHREAD_CHECK_RET(pthread_mutex_lock(&ready->mutex)); - ready->ready = 1; - ready->port = port; - PTHREAD_CHECK_RET(pthread_cond_signal(&ready->cond)); - PTHREAD_CHECK_RET(pthread_mutex_unlock(&ready->mutex)); - } + /* signal ready to accept data */ + tcp_ready* ready = args->signal; + PTHREAD_CHECK_RET(pthread_mutex_lock(&ready->mutex)); + ready->ready = 1; + ready->port = port; + PTHREAD_CHECK_RET(pthread_cond_signal(&ready->cond)); + PTHREAD_CHECK_RET(pthread_mutex_unlock(&ready->mutex)); #elif defined (WOLFSSL_TIRTOS) - /* Need mutex? */ - tcp_ready* ready = args->signal; - ready->ready = 1; - ready->port = port; + /* Need mutex? */ + tcp_ready* ready = args->signal; + ready->ready = 1; + ready->port = port; #elif defined(NETOS) - { tcp_ready* ready = args->signal; (void)tx_mutex_get(&ready->mutex, TX_WAIT_FOREVER); ready->ready = 1; ready->port = port; (void)tx_mutex_put(&ready->mutex); - } #else - (void)port; + (void)port; #endif + } + else { + fprintf(stderr, "args or args->signal was NULL. Not setting ready info."); + } *clientfd = *sockfd; }