Drop the SNI of a failed handshake on the Mbed TLS server

The Mbed TLS server keeps the SNI from the ClientHello in a
thread-local until the handshake succeeds, and its SNI callback only
runs for a ClientHello that carries one. A handshake that failed left
its SNI behind, so the next connection served by the same thread
without SNI reported the earlier client's name in req.sni(). Start
each handshake with the thread-local cleared.
This commit is contained in:
yhirose committed 2026-10-21 16:29:56 -04:00
1 parent 71b5f0a1c8
commit 7a9cee0e05
2 files changed
+44 -2

No files matched your search

+6 -2
View File
@@ -21467,6 +21467,9 @@ inline TlsError connect(session_t session) {
} }
inline TlsError accept(session_t session) { inline TlsError accept(session_t session) {
// A failed handshake leaves its SNI behind, so start without it
impl::mbedpending_sni().clear();
// Same as connect for Mbed TLS - handshake works for both client and server // Same as connect for Mbed TLS - handshake works for both client and server
auto result = connect(session); auto result = connect(session);
@@ -21474,7 +21477,6 @@ inline TlsError accept(session_t session) {
if (result.code == ErrorCode::Success && session) { if (result.code == ErrorCode::Success && session) {
auto msession = static_cast<impl::MbedTlsSession *>(session); auto msession = static_cast<impl::MbedTlsSession *>(session);
msession->sni_hostname = std::move(impl::mbedpending_sni()); msession->sni_hostname = std::move(impl::mbedpending_sni());
impl::mbedpending_sni().clear();
} }
return result; return result;
@@ -21519,6 +21521,9 @@ inline bool connect_nonblocking(session_t session, socket_t sock,
inline bool accept_nonblocking(session_t session, socket_t sock, inline bool accept_nonblocking(session_t session, socket_t sock,
time_t timeout_sec, time_t timeout_usec, time_t timeout_sec, time_t timeout_usec,
TlsError *err) { TlsError *err) {
// A failed handshake leaves its SNI behind, so start without it
impl::mbedpending_sni().clear();
// Same implementation as connect for Mbed TLS // Same implementation as connect for Mbed TLS
bool result = bool result =
connect_nonblocking(session, sock, timeout_sec, timeout_usec, err); connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
@@ -21527,7 +21532,6 @@ inline bool accept_nonblocking(session_t session, socket_t sock,
if (result && session) { if (result && session) {
auto msession = static_cast<impl::MbedTlsSession *>(session); auto msession = static_cast<impl::MbedTlsSession *>(session);
msession->sni_hostname = std::move(impl::mbedpending_sni()); msession->sni_hostname = std::move(impl::mbedpending_sni());
impl::mbedpending_sni().clear();
} }
return result; return result;
+38
View File
@@ -12977,6 +12977,44 @@ TEST(SNI_AutoDetectionTest, SNI_Logic) {
} }
} }
} }
// The SNI of a failed handshake does not reach the next connection that the
// same worker thread serves
TEST(SNI_AutoDetectionTest, FailedHandshakeLeavesNoSNI) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
svr.new_task_queue = [] { return new ThreadPool(1); };
svr.Get("/sni", [](const Request &req, Response &res) {
res.set_content(req.sni(), "text/plain");
});
auto port = svr.bind_to_any_port("127.0.0.1");
ASSERT_LT(0, port);
thread t = thread([&] { svr.listen_after_bind(); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
// Sends SNI, then rejects the server certificate, which the CA did not sign
SSLClient cli("localhost", port);
cli.set_hostname_addr_map({{"localhost", "127.0.0.1"}});
cli.set_ca_cert_path(CLIENT_CA_CERT_FILE);
auto res = cli.Get("/sni");
ASSERT_FALSE(res);
EXPECT_EQ(Error::SSLServerVerification, res.error());
}
// An IP host sends no SNI (RFC 6066)
SSLClient cli("127.0.0.1", port);
cli.enable_server_certificate_verification(false);
auto res = cli.Get("/sni");
ASSERT_TRUE(res) << "Error: " << to_string(res.error());
EXPECT_EQ("", res->body);
}
#endif #endif
TEST(ClientProblemDetectionTest, ContentProvider) { TEST(ClientProblemDetectionTest, ContentProvider) {