Skip to content

Commit d2a8735

Browse files
committed
Add WOLFSSL_MAX_EMPTY_RECORDS
1 parent 1f75deb commit d2a8735

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
@@ -3687,6 +3687,156 @@ int test_tls13_pqc_hybrid_truncated_keyshare(void)
36873687
* (32 bytes) does not cause an unsigned integer underflow / OOB read in
36883688
* SetTicket. Uses a full memio handshake, then injects a crafted
36893689
* NewSessionTicket with a 5-byte ticket into the client's read path. */
3690+
int test_tls13_empty_record_limit(void)
3691+
{
3692+
EXPECT_DECLS;
3693+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13)
3694+
struct test_memio_ctx test_ctx;
3695+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
3696+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
3697+
int recSz;
3698+
int numRecs = WOLFSSL_MAX_EMPTY_RECORDS + 1;
3699+
byte rec[128]; /* buffer for one encrypted record */
3700+
byte *allRecs = NULL;
3701+
int i;
3702+
char buf[64];
3703+
3704+
/* Test 1: Exceeding the empty record limit returns an error. */
3705+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
3706+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
3707+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
3708+
3709+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
3710+
3711+
/* Consume any post-handshake messages (e.g. NewSessionTicket). */
3712+
wolfSSL_read(ssl_c, buf, sizeof(buf));
3713+
test_memio_clear_buffer(&test_ctx, 0);
3714+
test_memio_clear_buffer(&test_ctx, 1);
3715+
3716+
/* Get the size of an encrypted zero-length app data record. */
3717+
recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0,
3718+
application_data, 0, 1, 0);
3719+
ExpectIntGT(recSz, 0);
3720+
ExpectIntLE(recSz, (int)sizeof(rec));
3721+
3722+
/* Build all empty records into one contiguous buffer. */
3723+
if (EXPECT_SUCCESS()) {
3724+
allRecs = (byte*)XMALLOC((size_t)(recSz * numRecs), NULL,
3725+
DYNAMIC_TYPE_TMP_BUFFER);
3726+
ExpectNotNull(allRecs);
3727+
}
3728+
3729+
for (i = 0; i < numRecs && EXPECT_SUCCESS(); i++) {
3730+
XMEMSET(rec, 0, sizeof(rec));
3731+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec), rec +
3732+
RECORD_HEADER_SZ, 0, application_data, 0, 0, 0),
3733+
recSz);
3734+
XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz);
3735+
}
3736+
3737+
/* Inject all records as a single message. */
3738+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)allRecs,
3739+
recSz * numRecs), 0);
3740+
3741+
/* The server's wolfSSL_read should fail with EMPTY_RECORD_LIMIT_E. */
3742+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)),
3743+
WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
3744+
ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR),
3745+
WC_NO_ERR_TRACE(EMPTY_RECORD_LIMIT_E));
3746+
3747+
XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER);
3748+
allRecs = NULL;
3749+
wolfSSL_free(ssl_c);
3750+
ssl_c = NULL;
3751+
wolfSSL_free(ssl_s);
3752+
ssl_s = NULL;
3753+
wolfSSL_CTX_free(ctx_c);
3754+
ctx_c = NULL;
3755+
wolfSSL_CTX_free(ctx_s);
3756+
ctx_s = NULL;
3757+
3758+
/* Test 2: Counter resets on non-empty record.
3759+
* Send (limit - 1) empty records, then 1 non-empty, then (limit - 1)
3760+
* more empty records. Should succeed without hitting the limit. */
3761+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
3762+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
3763+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
3764+
3765+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
3766+
3767+
wolfSSL_read(ssl_c, buf, sizeof(buf));
3768+
test_memio_clear_buffer(&test_ctx, 0);
3769+
test_memio_clear_buffer(&test_ctx, 1);
3770+
3771+
recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0,
3772+
application_data, 0, 1, 0);
3773+
ExpectIntGT(recSz, 0);
3774+
3775+
{
3776+
int emptyBefore = WOLFSSL_MAX_EMPTY_RECORDS - 1;
3777+
int emptyAfter = WOLFSSL_MAX_EMPTY_RECORDS - 1;
3778+
int dataRecSz;
3779+
byte dataRec[128];
3780+
byte payload[1] = { 'a' };
3781+
int totalSz;
3782+
3783+
dataRecSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 1,
3784+
application_data, 0, 1, 0);
3785+
ExpectIntGT(dataRecSz, 0);
3786+
3787+
totalSz = recSz * (emptyBefore + emptyAfter) + dataRecSz;
3788+
if (EXPECT_SUCCESS()) {
3789+
allRecs = (byte*)XMALLOC((size_t)totalSz, NULL,
3790+
DYNAMIC_TYPE_TMP_BUFFER);
3791+
ExpectNotNull(allRecs);
3792+
}
3793+
3794+
/* Build (limit - 1) empty records */
3795+
for (i = 0; i < emptyBefore && EXPECT_SUCCESS(); i++) {
3796+
XMEMSET(rec, 0, sizeof(rec));
3797+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec),
3798+
rec + RECORD_HEADER_SZ, 0, application_data,
3799+
0, 0, 0), recSz);
3800+
XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz);
3801+
}
3802+
3803+
/* Build 1 non-empty record */
3804+
XMEMSET(dataRec, 0, sizeof(dataRec));
3805+
XMEMCPY(dataRec + RECORD_HEADER_SZ, payload, sizeof(payload));
3806+
ExpectIntEQ(BuildTls13Message(ssl_c, dataRec, (int)sizeof(dataRec),
3807+
dataRec + RECORD_HEADER_SZ, 1, application_data,
3808+
0, 0, 0), dataRecSz);
3809+
XMEMCPY(allRecs + emptyBefore * recSz, dataRec, (size_t)dataRecSz);
3810+
3811+
/* Build (limit - 1) more empty records */
3812+
for (i = 0; i < emptyAfter && EXPECT_SUCCESS(); i++) {
3813+
XMEMSET(rec, 0, sizeof(rec));
3814+
ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec),
3815+
rec + RECORD_HEADER_SZ, 0, application_data,
3816+
0, 0, 0), recSz);
3817+
XMEMCPY(allRecs + emptyBefore * recSz + dataRecSz + i * recSz,
3818+
rec, (size_t)recSz);
3819+
}
3820+
3821+
ExpectIntEQ(test_memio_inject_message(&test_ctx, 0,
3822+
(const char*)allRecs, totalSz), 0);
3823+
}
3824+
3825+
/* wolfSSL_read should return the 1-byte payload. The counter resets
3826+
* on the non-empty record so neither batch of (limit - 1) empties
3827+
* triggers the error. */
3828+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), 1);
3829+
ExpectIntEQ(buf[0], 'a');
3830+
3831+
XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER);
3832+
wolfSSL_free(ssl_c);
3833+
wolfSSL_free(ssl_s);
3834+
wolfSSL_CTX_free(ctx_c);
3835+
wolfSSL_CTX_free(ctx_s);
3836+
#endif
3837+
return EXPECT_RESULT();
3838+
}
3839+
36903840
int test_tls13_short_session_ticket(void)
36913841
{
36923842
EXPECT_DECLS;

tests/api/test_tls13.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ int test_tls13_warning_alert_is_fatal(void);
4444
int test_tls13_cert_req_sigalgs(void);
4545
int test_tls13_derive_keys_no_key(void);
4646
int test_tls13_pqc_hybrid_truncated_keyshare(void);
47+
int test_tls13_empty_record_limit(void);
4748
int test_tls13_short_session_ticket(void);
4849

4950
#define TEST_TLS13_DECLS \
@@ -67,6 +68,7 @@ int test_tls13_short_session_ticket(void);
6768
TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \
6869
TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \
6970
TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \
71+
TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \
7072
TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket)
7173

7274
#endif /* WOLFCRYPT_TEST_TLS13_H */

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
@@ -5218,6 +5218,7 @@ struct Options {
52185218
byte asyncState; /* sub-state for enum asyncState */
52195219
byte buildMsgState; /* sub-state for enum buildMsgState */
52205220
byte alertCount; /* detect warning dos attempt */
5221+
byte emptyRecordCount; /* detect empty record dos attempt */
52215222
#ifdef WOLFSSL_MULTICAST
52225223
word16 mcastID; /* Multicast group ID */
52235224
#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)