Skip to content

Commit a05413a

Browse files
committed
Add WOLFSSL_MAX_EMPTY_RECORDS
1 parent e86f84d commit a05413a

6 files changed

Lines changed: 180 additions & 1 deletion

File tree

src/internal.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21862,6 +21862,23 @@ int DoApplicationData(WOLFSSL* ssl, byte* input, word32* inOutIdx, int sniff)
2186221862
}
2186321863
#endif
2186421864

21865+
/* Rate-limit empty application data records to prevent DoS */
21866+
if (dataSz == 0) {
21867+
if (++ssl->options.emptyRecordCount >= WOLFSSL_MAX_EMPTY_RECORDS) {
21868+
WOLFSSL_MSG("Too many empty records");
21869+
#ifdef WOLFSSL_EXTRA_ALERTS
21870+
if (sniff == NO_SNIFF) {
21871+
SendAlert(ssl, alert_fatal, unexpected_message);
21872+
}
21873+
#endif
21874+
WOLFSSL_ERROR_VERBOSE(EMPTY_RECORD_LIMIT_E);
21875+
return EMPTY_RECORD_LIMIT_E;
21876+
}
21877+
}
21878+
else {
21879+
ssl->options.emptyRecordCount = 0;
21880+
}
21881+
2186521882
/* read data */
2186621883
if (dataSz) {
2186721884
int rawSz = dataSz; /* keep raw size for idx adjustment */
@@ -27573,6 +27590,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e)
2757327590
case ALERT_COUNT_E:
2757427591
return "Alert Count exceeded error";
2757527592

27593+
case EMPTY_RECORD_LIMIT_E:
27594+
return "Too many empty records error";
27595+
2757627596
case EXT_MISSING:
2757727597
return "Required TLS extension missing";
2757827598

tests/api/test_tls13.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3779,6 +3779,156 @@ int test_tls13_pqc_hybrid_truncated_keyshare(void)
37793779
* (32 bytes) does not cause an unsigned integer underflow / OOB read in
37803780
* SetTicket. Uses a full memio handshake, then injects a crafted
37813781
* NewSessionTicket with a 5-byte ticket into the client's read path. */
3782+
int test_tls13_empty_record_limit(void)
3783+
{
3784+
EXPECT_DECLS;
3785+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13)
3786+
struct test_memio_ctx test_ctx;
3787+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
3788+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
3789+
int recSz;
3790+
int numRecs = WOLFSSL_MAX_EMPTY_RECORDS + 1;
3791+
byte rec[128]; /* buffer for one encrypted record */
3792+
byte *allRecs = NULL;
3793+
int i;
3794+
char buf[64];
3795+
3796+
/* Test 1: Exceeding the empty record limit returns an error. */
3797+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
3798+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
3799+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
3800+
3801+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
3802+
3803+
/* Consume any post-handshake messages (e.g. NewSessionTicket). */
3804+
wolfSSL_read(ssl_c, buf, sizeof(buf));
3805+
test_memio_clear_buffer(&test_ctx, 0);
3806+
test_memio_clear_buffer(&test_ctx, 1);
3807+
3808+
/* Get the size of an encrypted zero-length app data record. */
3809+
recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0,
3810+
application_data, 0, 1, 0);
3811+
ExpectIntGT(recSz, 0);
3812+
ExpectIntLE(recSz, (int)sizeof(rec));
3813+
3814+
/* Build all empty records into one contiguous buffer. */
3815+
if (EXPECT_SUCCESS()) {
3816+
allRecs = (byte*)XMALLOC((size_t)(recSz * numRecs), NULL,
3817+
DYNAMIC_TYPE_TMP_BUFFER);
3818+
ExpectNotNull(allRecs);
3819+
}
3820+
3821+
for (i = 0; i < numRecs && EXPECT_SUCCESS(); i++) {
3822+
XMEMSET(rec, 0, sizeof(rec));
3823+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec), rec +
3824+
RECORD_HEADER_SZ, 0, application_data, 0, 0, 0),
3825+
recSz);
3826+
XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz);
3827+
}
3828+
3829+
/* Inject all records as a single message. */
3830+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)allRecs,
3831+
recSz * numRecs), 0);
3832+
3833+
/* The server's wolfSSL_read should fail with EMPTY_RECORD_LIMIT_E. */
3834+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)),
3835+
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
3836+
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
3837+
WC_NO_ERR_TRACE(EMPTY_RECORD_LIMIT_E));
3838+
3839+
XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER);
3840+
allRecs = NULL;
3841+
wolfSSL_free(ssl_c);
3842+
ssl_c = NULL;
3843+
wolfSSL_free(ssl_s);
3844+
ssl_s = NULL;
3845+
wolfSSL_CTX_free(ctx_c);
3846+
ctx_c = NULL;
3847+
wolfSSL_CTX_free(ctx_s);
3848+
ctx_s = NULL;
3849+
3850+
/* Test 2: Counter resets on non-empty record.
3851+
* Send (limit - 1) empty records, then 1 non-empty, then (limit - 1)
3852+
* more empty records. Should succeed without hitting the limit. */
3853+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
3854+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
3855+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
3856+
3857+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
3858+
3859+
wolfSSL_read(ssl_c, buf, sizeof(buf));
3860+
test_memio_clear_buffer(&test_ctx, 0);
3861+
test_memio_clear_buffer(&test_ctx, 1);
3862+
3863+
recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0,
3864+
application_data, 0, 1, 0);
3865+
ExpectIntGT(recSz, 0);
3866+
3867+
{
3868+
int emptyBefore = WOLFSSL_MAX_EMPTY_RECORDS - 1;
3869+
int emptyAfter = WOLFSSL_MAX_EMPTY_RECORDS - 1;
3870+
int dataRecSz;
3871+
byte dataRec[128];
3872+
byte payload[1] = { 'a' };
3873+
int totalSz;
3874+
3875+
dataRecSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 1,
3876+
application_data, 0, 1, 0);
3877+
ExpectIntGT(dataRecSz, 0);
3878+
3879+
totalSz = recSz * (emptyBefore + emptyAfter) + dataRecSz;
3880+
if (EXPECT_SUCCESS()) {
3881+
allRecs = (byte*)XMALLOC((size_t)totalSz, NULL,
3882+
DYNAMIC_TYPE_TMP_BUFFER);
3883+
ExpectNotNull(allRecs);
3884+
}
3885+
3886+
/* Build (limit - 1) empty records */
3887+
for (i = 0; i < emptyBefore && EXPECT_SUCCESS(); i++) {
3888+
XMEMSET(rec, 0, sizeof(rec));
3889+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec),
3890+
rec + RECORD_HEADER_SZ, 0, application_data,
3891+
0, 0, 0), recSz);
3892+
XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz);
3893+
}
3894+
3895+
/* Build 1 non-empty record */
3896+
XMEMSET(dataRec, 0, sizeof(dataRec));
3897+
XMEMCPY(dataRec + RECORD_HEADER_SZ, payload, sizeof(payload));
3898+
ExpectIntEQ(BuildTls13Message(ssl_c, dataRec, (int)sizeof(dataRec),
3899+
dataRec + RECORD_HEADER_SZ, 1, application_data,
3900+
0, 0, 0), dataRecSz);
3901+
XMEMCPY(allRecs + emptyBefore * recSz, dataRec, (size_t)dataRecSz);
3902+
3903+
/* Build (limit - 1) more empty records */
3904+
for (i = 0; i < emptyAfter && EXPECT_SUCCESS(); i++) {
3905+
XMEMSET(rec, 0, sizeof(rec));
3906+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec),
3907+
rec + RECORD_HEADER_SZ, 0, application_data,
3908+
0, 0, 0), recSz);
3909+
XMEMCPY(allRecs + emptyBefore * recSz + dataRecSz + i * recSz,
3910+
rec, (size_t)recSz);
3911+
}
3912+
3913+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0,
3914+
(const char*)allRecs, totalSz), 0);
3915+
}
3916+
3917+
/* wolfSSL_read should return the 1-byte payload. The counter resets
3918+
* on the non-empty record so neither batch of (limit - 1) empties
3919+
* triggers the error. */
3920+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), 1);
3921+
ExpectIntEQ(buf[0], 'a');
3922+
3923+
XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER);
3924+
wolfSSL_free(ssl_c);
3925+
wolfSSL_free(ssl_s);
3926+
wolfSSL_CTX_free(ctx_c);
3927+
wolfSSL_CTX_free(ctx_s);
3928+
#endif
3929+
return EXPECT_RESULT();
3930+
}
3931+
37823932
int test_tls13_short_session_ticket(void)
37833933
{
37843934
EXPECT_DECLS;

tests/api/test_tls13.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ int test_tls13_unknown_ext_rejected(void);
4545
int test_tls13_cert_req_sigalgs(void);
4646
int test_tls13_derive_keys_no_key(void);
4747
int test_tls13_pqc_hybrid_truncated_keyshare(void);
48+
int test_tls13_empty_record_limit(void);
4849
int test_tls13_short_session_ticket(void);
4950

5051
#define TEST_TLS13_DECLS \
@@ -68,6 +69,7 @@ int test_tls13_short_session_ticket(void);
6869
TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \
6970
TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \
7071
TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \
72+
TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \
7173
TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket), \
7274
TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected)
7375

wolfssl/error-ssl.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ enum wolfSSL_ErrorCodes {
240240

241241
SESSION_TICKET_NONCE_OVERFLOW = -517, /* Session ticket nonce overflow */
242242

243-
WOLFSSL_LAST_E = -517
243+
EMPTY_RECORD_LIMIT_E = -518, /* Too many empty records received */
244+
245+
WOLFSSL_LAST_E = -518
244246

245247
/* codes -1000 to -1999 are reserved for wolfCrypt. */
246248
};

wolfssl/internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5216,6 +5216,7 @@ struct Options {
52165216
byte asyncState; /* sub-state for enum asyncState */
52175217
byte buildMsgState; /* sub-state for enum buildMsgState */
52185218
byte alertCount; /* detect warning dos attempt */
5219+
byte emptyRecordCount; /* detect empty record dos attempt */
52195220
#ifdef WOLFSSL_MULTICAST
52205221
word16 mcastID; /* Multicast group ID */
52215222
#endif

wolfssl/wolfcrypt/settings.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4144,6 +4144,10 @@ extern void uITRON4_free(void *p) ;
41444144
#define WOLFSSL_ALERT_COUNT_MAX 5
41454145
#endif
41464146

4147+
#ifndef WOLFSSL_MAX_EMPTY_RECORDS
4148+
#define WOLFSSL_MAX_EMPTY_RECORDS 32
4149+
#endif
4150+
41474151
/* Enable blinding by default for C-only, non-small curve25519 implementation */
41484152
#if defined(HAVE_CURVE25519) && !defined(CURVE25519_SMALL) && \
41494153
!defined(FREESCALE_LTC_ECC) && !defined(WOLFSSL_ARMASM) && \

0 commit comments

Comments
 (0)