You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by du...@apache.org on 2023/03/24 09:01:48 UTC

[shardingsphere] branch master updated: Gracefully handling bad handshake in MySQL Proxy (#24802)

This is an automated email from the ASF dual-hosted git repository.

duanzhengqiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 6aa1d9d41ed Gracefully handling bad handshake in MySQL Proxy (#24802)
6aa1d9d41ed is described below

commit 6aa1d9d41edde3bc1e7e539b6c241d0398bc8ee7
Author: 吴伟杰 <wu...@apache.org>
AuthorDate: Fri Mar 24 17:01:39 2023 +0800

    Gracefully handling bad handshake in MySQL Proxy (#24802)
    
    * Gracefully handling bad handshake in MySQL Proxy
    
    * Complete MySQLAuthenticationEngineTest
    
    * Fix checkstyle in MySQLAuthenticationEngineTest
---
 .../dialect/mysql/vendor/MySQLVendorError.java     |  2 ++
 .../external/sql/sqlstate/XOpenSQLState.java       |  2 ++
 .../authentication/MySQLAuthenticationEngine.java  | 14 +++++++++++-
 .../MySQLAuthenticationEngineTest.java             | 26 +++++++++++++++++++++-
 4 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/dialect-exception/mysql/src/main/java/org/apache/shardingsphere/dialect/mysql/vendor/MySQLVendorError.java b/dialect-exception/mysql/src/main/java/org/apache/shardingsphere/dialect/mysql/vendor/MySQLVendorError.java
index b454eabb600..cdd20e90230 100644
--- a/dialect-exception/mysql/src/main/java/org/apache/shardingsphere/dialect/mysql/vendor/MySQLVendorError.java
+++ b/dialect-exception/mysql/src/main/java/org/apache/shardingsphere/dialect/mysql/vendor/MySQLVendorError.java
@@ -32,6 +32,8 @@ import org.apache.shardingsphere.infra.util.exception.external.sql.vendor.Vendor
 @Getter
 public enum MySQLVendorError implements VendorError {
     
+    ER_HANDSHAKE_ERROR(XOpenSQLState.COMMUNICATION_LINK_FAILURE, 1043, "Bad handshake"),
+    
     ER_DBACCESS_DENIED_ERROR(XOpenSQLState.SYNTAX_ERROR, 1044, "Access denied for user '%s'@'%s' to database '%s'"),
     
     ER_ACCESS_DENIED_ERROR(XOpenSQLState.INVALID_AUTHORIZATION_SPECIFICATION, 1045, "Access denied for user '%s'@'%s' (using password: %s)"),
diff --git a/infra/util/src/main/java/org/apache/shardingsphere/infra/util/exception/external/sql/sqlstate/XOpenSQLState.java b/infra/util/src/main/java/org/apache/shardingsphere/infra/util/exception/external/sql/sqlstate/XOpenSQLState.java
index 83c9707a96f..6ec0ff730ba 100644
--- a/infra/util/src/main/java/org/apache/shardingsphere/infra/util/exception/external/sql/sqlstate/XOpenSQLState.java
+++ b/infra/util/src/main/java/org/apache/shardingsphere/infra/util/exception/external/sql/sqlstate/XOpenSQLState.java
@@ -37,6 +37,8 @@ public enum XOpenSQLState implements SQLState {
     
     DATA_SOURCE_REJECTED_CONNECTION_ATTEMPT("08004"),
     
+    COMMUNICATION_LINK_FAILURE("08S01"),
+    
     FEATURE_NOT_SUPPORTED("0A000"),
     
     MISMATCH_INSERT_VALUES_AND_COLUMNS("21S01"),
diff --git a/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java b/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
index d3be1b783e6..b7a90d32787 100644
--- a/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
+++ b/proxy/frontend/type/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
@@ -18,8 +18,10 @@
 package org.apache.shardingsphere.proxy.frontend.mysql.authentication;
 
 import com.google.common.base.Strings;
+import io.netty.buffer.ByteBufUtil;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.epoll.EpollDomainSocketChannel;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.shardingsphere.authority.checker.AuthorityChecker;
 import org.apache.shardingsphere.authority.rule.AuthorityRule;
 import org.apache.shardingsphere.db.protocol.constant.CommonConstants;
@@ -57,6 +59,7 @@ import java.util.Optional;
 /**
  * Authentication engine for MySQL.
  */
+@Slf4j
 public final class MySQLAuthenticationEngine implements AuthenticationEngine {
     
     private final MySQLAuthenticationPluginData authPluginData = new MySQLAuthenticationPluginData();
@@ -103,7 +106,16 @@ public final class MySQLAuthenticationEngine implements AuthenticationEngine {
     }
     
     private AuthenticationResult authenticatePhaseFastPath(final ChannelHandlerContext context, final PacketPayload payload, final AuthorityRule rule) {
-        MySQLHandshakeResponse41Packet handshakeResponsePacket = new MySQLHandshakeResponse41Packet((MySQLPacketPayload) payload);
+        MySQLHandshakeResponse41Packet handshakeResponsePacket;
+        try {
+            handshakeResponsePacket = new MySQLHandshakeResponse41Packet((MySQLPacketPayload) payload);
+        } catch (IndexOutOfBoundsException ex) {
+            writeErrorPacket(context, new MySQLErrPacket(MySQLVendorError.ER_HANDSHAKE_ERROR));
+            if (log.isWarnEnabled()) {
+                log.warn("Received bad handshake from client {}: \n{}", context.channel(), ByteBufUtil.prettyHexDump(payload.getByteBuf().resetReaderIndex()));
+            }
+            return AuthenticationResultBuilder.continued();
+        }
         String database = handshakeResponsePacket.getDatabase();
         authResponse = handshakeResponsePacket.getAuthResponse();
         setCharacterSet(context, handshakeResponsePacket);
diff --git a/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java b/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
index 487562ce791..22f4c32e9e6 100644
--- a/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
+++ b/proxy/frontend/type/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.proxy.frontend.mysql.authentication;
 
+import io.netty.buffer.Unpooled;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.util.Attribute;
@@ -37,9 +38,9 @@ import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
 import org.apache.shardingsphere.infra.metadata.user.Grantee;
 import org.apache.shardingsphere.infra.metadata.user.ShardingSphereUser;
+import org.apache.shardingsphere.metadata.persist.MetaDataPersistService;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
-import org.apache.shardingsphere.metadata.persist.MetaDataPersistService;
 import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
 import org.apache.shardingsphere.proxy.frontend.authentication.AuthenticationResultBuilder;
 import org.apache.shardingsphere.proxy.frontend.authentication.Authenticator;
@@ -49,12 +50,14 @@ import org.apache.shardingsphere.test.mock.StaticMockSettings;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.MockedConstruction;
+import org.mockito.MockedConstruction.Context;
 import org.mockito.internal.configuration.plugins.Plugins;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -88,6 +91,27 @@ public final class MySQLAuthenticationEngineTest {
         verify(context).writeAndFlush(any(MySQLHandshakePacket.class));
     }
     
+    @Test
+    public void assertBadHandshakeReceived() {
+        AuthorityRule rule = mock(AuthorityRule.class);
+        when(rule.getAuthenticatorType(any())).thenReturn("");
+        ContextManager contextManager = mockContextManager(rule);
+        when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+        ChannelHandlerContext context = mockChannelHandlerContext();
+        authenticationEngine.handshake(context);
+        try (MockedConstruction<MySQLErrPacket> ignored = mockConstruction(MySQLErrPacket.class, this::assertBadHandshakeError)) {
+            authenticationEngine.authenticate(context, new MySQLPacketPayload(Unpooled.wrappedBuffer(new byte[]{0x02, 0x03}), StandardCharsets.UTF_8));
+            verify(context).writeAndFlush(any(MySQLErrPacket.class));
+            verify(context).close();
+        }
+    }
+    
+    private void assertBadHandshakeError(final MySQLErrPacket mock, final Context mockContext) {
+        List<?> arguments = mockContext.arguments();
+        assertThat(arguments.get(0), is(MySQLVendorError.ER_HANDSHAKE_ERROR));
+        assertThat(arguments.get(1), is(new Object[0]));
+    }
+    
     @SuppressWarnings("unchecked")
     @Test
     public void assertAuthenticationMethodMismatch() {