https://bugs.gentoo.org/977642

From 33d66d22d8bf9c86af307307004ed43850face5f Mon Sep 17 00:00:00 2001
From: Harin Vadodaria <harin.vadodaria@oracle.com>
Date: Mon, 1 Jun 2026 10:15:59 +0200
Subject: [PATCH 1/2] Bug#39116965: MySQL Out-of-Bounds Read

Description:
Connection attribute parsing read a length-encoded size field before
checking that the complete field was present in the packet.

Fix:
A size check was added before reading the field. Debug coverage was
added for short length-encoded attribute packets during
pre-authentication.

Change-Id: Ia90e387a219556bb040a26853946c022f73de730
--- a/sql/auth/sql_authentication.cc
+++ b/sql/auth/sql_authentication.cc
@@ -2262,9 +2262,20 @@ static bool read_client_connect_attrs(THD *thd, char **ptr,
   size_t length, length_length;
   char *ptr_save;
 
-  /* not enough bytes to hold the length */
+  /* Need one byte to determine the length-encoded field size. */
   if (*max_bytes_available < 1) return true;
 
+  uchar *pos = (uchar *)*ptr;
+  DBUG_EXECUTE_IF("connect_attrs_too_short_3", *pos = 252;
+                  *max_bytes_available = 2;);
+  DBUG_EXECUTE_IF("connect_attrs_too_short_4", *pos = 253;
+                  *max_bytes_available = 3;);
+  DBUG_EXECUTE_IF("connect_attrs_too_short_9", *pos = 254;
+                  *max_bytes_available = 8;);
+
+  const size_t required_length = (size_t)net_field_length_size(pos);
+  if (*max_bytes_available < required_length) return true;
+
   /* read the length */
   ptr_save = *ptr;
   length = static_cast<size_t>(net_field_length_ll((uchar **)ptr));
-- 
2.54.0


From 2747a03d7a522a4f880df5c978f2e5c1db17b119 Mon Sep 17 00:00:00 2001
From: Lukasz Kotula <lukasz.kotula@oracle.com>
Date: Fri, 17 Apr 2026 09:12:57 +0200
Subject: [PATCH 2/2] Bug#39204635 - Unauthenticated repeated X Protocol TLS
 upgrade crashes MySQL Router

Description
For X Protocol routing with `client_ssl_mode=PREFERRED` and
`server_ssl_mode=AS_CLIENT`, a client can send
`CON_CAPABILITIES_SET(tls=true)` after TLS is already active.

Router accepted this second TLS capability request and re-entered
the TLS upgrade path. During `tls_accept_finalize()`, the server-side
TLS channel could already be initialized, which led to a `logic_error`
and connection termination.

Fix
===
Reject repeated TLS capability upgrades when client-side TLS is already
active. For a second `tls=true` capability request, Router now returns
error 5001 (`Capability prepare failed for 'tls'`) and continues normal
session handling.

Change-Id: Ifaaa93eeec1019160c7a6db50daa7e1af8f383c5
--- a/router/src/routing/src/x_connection.cc
+++ b/router/src/routing/src/x_connection.cc
@@ -1471,6 +1471,7 @@ void MysqlRoutingXConnection::client_cap_set() {
 
   if (switch_to_tls) {
     bool continue_with_tls{false};
+
     switch (source_ssl_mode()) {
       case SslMode::kDisabled: {
         continue_with_tls = false;
@@ -1525,6 +1526,11 @@ void MysqlRoutingXConnection::client_cap_set() {
     discard_current_msg(src_channel, src_protocol);
     std::vector<uint8_t> out_buf;
 
+    // The client tries to activate TLS when it is already enabled.
+    if (continue_with_tls && src_channel->ssl()) {
+      continue_with_tls = false;
+    }
+
     if (!continue_with_tls) {
 #ifdef DEBUG_IO
       std::cerr << __LINE__ << ": "
--- a/router/tests/component/test_routing.cc
+++ b/router/tests/component/test_routing.cc
@@ -122,6 +122,23 @@ static xcl::XError make_x_connection(XProtocolSession &session,
                           password.c_str(), "");
 }
 
+static xcl::XError make_x_raw_connection(
+    XProtocolSession &session, const std::string &host, const uint16_t port,
+    int64_t connect_timeout = 10000 /*10s*/) {
+  session = xcl::create_session();
+  xcl::XError err = setup_x_session(session, connect_timeout, "PREFERRED");
+  if (err) return err;
+
+  return session->get_protocol().get_connection().connect(
+      host, port, xcl::Internet_protocol::Any);
+}
+
+static std::string format_xerror(const xcl::XError &e) {
+  if (!e) return "no-error";
+
+  return "(code:" + std::to_string(e.error()) + ", message:" + e.what() + ")";
+}
+
 TEST_F(RouterRoutingTest, RoutingOk) {
   const auto server_port = port_pool_.get_next_available();
   const auto router_port = port_pool_.get_next_available();
@@ -1882,6 +1899,78 @@ static size_t xproto_frame_encode(const T &msg, uint8_t msg_type,
   return msg.SerializeToCodedStream(&codecouts);
 }
 
+/**
+ * @test Verify that repeated TLS activation requests are properly rejected
+ * and do not crash the Router.
+ */
+TEST_F(RouterRoutingTest, XProtocolRepeatedTlsUpgrade) {
+  const auto server_classic_port = port_pool_.get_next_available();
+  const auto server_x_port = port_pool_.get_next_available();
+  const auto router_x_rw_port = port_pool_.get_next_available();
+
+  const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
+
+  launch_mysql_server_mock(json_stmts, server_classic_port, EXIT_SUCCESS, false,
+                           /*http_port*/ 0, server_x_port, /*module_prefix*/ "",
+                           /*bind_address*/ "127.0.0.1",
+                           /*wait_for_notify_ready*/ std::chrono::seconds(30),
+                           /*enable_ssl*/ true);
+
+  const std::string routing_x_section = get_static_routing_section(
+      "x", router_x_rw_port, "", {server_x_port}, "x");
+
+  TempDirectory conf_dir("conf");
+  const std::string ssl_conf =
+      "client_ssl_mode=PREFERRED\n"
+      "server_ssl_mode=AS_CLIENT\n"
+      "client_ssl_key=" SSL_TEST_DATA_DIR
+      "/server-key-sha512.pem\n"
+      "client_ssl_cert=" SSL_TEST_DATA_DIR "/server-cert-sha512.pem";
+  std::string conf_file =
+      create_config_file(conf_dir.name(), routing_x_section, nullptr,
+                         "mysqlrouter.conf", ssl_conf, true);
+
+  launch_router({"-c", conf_file});
+
+  Mysqlx::Connection::CapabilitiesSet switch_tls_msg;
+  auto *cap = switch_tls_msg.mutable_capabilities()->add_capabilities();
+  cap->set_name("tls");
+  auto *cap_value = cap->mutable_value();
+  cap_value->set_type(Mysqlx::Datatypes::Any_Type::Any_Type_SCALAR);
+  auto *cap_scalar = cap_value->mutable_scalar();
+  cap_scalar->set_type(Mysqlx::Datatypes::Scalar_Type::Scalar_Type_V_BOOL);
+  cap_scalar->set_v_bool(true);
+
+  XProtocolSession x_session;
+  const auto x_connect_error =
+      make_x_raw_connection(x_session, "127.0.0.1", router_x_rw_port);
+  ASSERT_FALSE(x_connect_error) << format_xerror(x_connect_error);
+
+  const auto x_cap_set_error =
+      x_session.get()->get_protocol().execute_set_capability(switch_tls_msg);
+  ASSERT_FALSE(x_cap_set_error) << format_xerror(x_cap_set_error);
+
+  const auto x_tls_error =
+      x_session.get()->get_protocol().get_connection().activate_tls();
+
+  ASSERT_FALSE(x_tls_error) << format_xerror(x_tls_error);
+
+  const auto x_cap_set2_error =
+      x_session.get()->get_protocol().execute_set_capability(switch_tls_msg);
+  ASSERT_EQ(x_cap_set2_error.error(), 5001) << format_xerror(x_cap_set2_error);
+
+  const auto x_login_error =
+      x_session.get()->get_protocol().execute_authenticate("root", "fake-pass",
+                                                           "", "SHA256_MEMORY");
+  ASSERT_FALSE(x_login_error) << format_xerror(x_login_error);
+
+  // Router should still accept a fresh X Protocol connection after the loop.
+  XProtocolSession x_session2;
+  const auto res = make_x_connection(x_session2, "127.0.0.1", router_x_rw_port,
+                                     "root", "fake-pass");
+  EXPECT_THAT(res.error(), ::testing::AnyOf(0, 3159));
+}
+
 /**
  * @test Check that if the x protocol client sends CONCLOSE message the Router
  * replies with OK{bye!} message.
-- 
2.54.0

