From 11fc781d0d30fc2afaa1f236d97296d9facff63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Mar 2026 12:30:59 +0100 Subject: [PATCH] Treat alerts as fatal errors regardless of level in TLS1.3 --- src/internal.c | 15 +++++++++++++ tests/api/test_tls13.c | 48 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 3 files changed, 65 insertions(+) diff --git a/src/internal.c b/src/internal.c index f7f0b26d644..a4cc2780df7 100644 --- a/src/internal.c +++ b/src/internal.c @@ -22036,6 +22036,12 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type) if (level == alert_fatal) { ssl->options.isClosed = 1; /* Don't send close_notify */ } + /* RFC 8446 Section 6.2: In TLS 1.3, all error alerts are implicitly + * fatal regardless of the AlertLevel byte. */ + if (IsAtLeastTLSv1_3(ssl->version) && + code != close_notify && code != user_canceled) { + ssl->options.isClosed = 1; + } } if (++ssl->options.alertCount >= WOLFSSL_ALERT_COUNT_MAX) { @@ -23510,6 +23516,15 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) if (type == decrypt_error) return FATAL_ERROR; + /* RFC 8446 Section 6.2: In TLS 1.3, all error alerts MUST + * be treated as fatal regardless of the AlertLevel byte. + * Only close_notify (handled above) and user_canceled + * are exempt. */ + if (IsAtLeastTLSv1_3(ssl->version) && + type != user_canceled && type != invalid_alert) { + return FATAL_ERROR; + } + /* Reset error if we got an alert level in ret */ if (ret > 0) ret = 0; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index a6f411544bc..4b60a20dcb6 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3100,6 +3100,54 @@ int test_tls13_plaintext_alert(void) return EXPECT_RESULT(); } +/* Test that TLS 1.3 warning-level alerts are treated as fatal (RFC 8446 + * Section 6.2). + * A peer sending e.g. {alert_warning, handshake_failure} must still cause the + * connection to be terminated, not silently continued. + */ +int test_tls13_warning_alert_is_fatal(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL *ssl_c = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + /* TLS record: content_type=alert(0x15), version=TLS1.2(0x0303), len=2, + * level=warning(0x01), code=handshake_failure(0x28=40) */ + static const unsigned char warn_alert[] = + { 0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x28 }; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, NULL, &ssl_c, NULL, + wolfTLSv1_3_client_method, NULL), 0); + + /* Client sends ClientHello, then waits for the server response. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + /* Inject a warning-level handshake_failure alert as if from the server. + * RFC 8446 Section 6.2: In TLS 1.3, all error alerts MUST be treated as + * fatalregardless of the AlertLevel byte. */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 1, + (const char *)warn_alert, sizeof(warn_alert)), 0); + + /* Expect the connection to be terminated, not silently continued. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WC_NO_ERR_TRACE(FATAL_ERROR)); + + /* The alert details should be recorded correctly. */ + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, handshake_failure); + ExpectIntEQ(h.last_rx.level, alert_warning); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + /* Test that wolfSSL_set1_sigalgs_list() is honored in TLS 1.3 */ int test_tls13_cert_req_sigalgs(void) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 67b23d57fe7..f5435e6b0e5 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -38,6 +38,7 @@ int test_tls13_duplicate_extension(void); int test_key_share_mismatch(void); int test_tls13_middlebox_compat_empty_session_id(void); int test_tls13_plaintext_alert(void); +int test_tls13_warning_alert_is_fatal(void); int test_tls13_cert_req_sigalgs(void); #define TEST_TLS13_DECLS \ @@ -55,6 +56,7 @@ int test_tls13_cert_req_sigalgs(void); TEST_DECL_GROUP("tls13", test_key_share_mismatch), \ TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_empty_session_id), \ TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \ + TEST_DECL_GROUP("tls13", test_tls13_warning_alert_is_fatal), \ TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs) #endif /* WOLFCRYPT_TEST_TLS13_H */