Merge branch '2.1' into 2.2
This commit is contained in:
@ -319,7 +319,7 @@ of the mask value to get the lengths to match.
|
|||||||
"column": "creditcard"
|
"column": "creditcard"
|
||||||
},
|
},
|
||||||
"with": {
|
"with": {
|
||||||
"value": "1234123412341234"
|
"value": "1234123412341234",
|
||||||
"fill": "0"
|
"fill": "0"
|
||||||
},
|
},
|
||||||
"applies_to": [ ... ],
|
"applies_to": [ ... ],
|
||||||
|
@ -130,6 +130,7 @@ typedef struct mxs_authenticator
|
|||||||
#define MXS_AUTH_SSL_INCOMPLETE 5 /**< SSL connection is not yet complete */
|
#define MXS_AUTH_SSL_INCOMPLETE 5 /**< SSL connection is not yet complete */
|
||||||
#define MXS_AUTH_SSL_COMPLETE 6 /**< SSL connection complete or not required */
|
#define MXS_AUTH_SSL_COMPLETE 6 /**< SSL connection complete or not required */
|
||||||
#define MXS_AUTH_NO_SESSION 7
|
#define MXS_AUTH_NO_SESSION 7
|
||||||
|
#define MXS_AUTH_BAD_HANDSHAKE 8 /**< Malformed client packet */
|
||||||
|
|
||||||
/** Return values for the loadusers entry point */
|
/** Return values for the loadusers entry point */
|
||||||
#define MXS_AUTH_LOADUSERS_OK 0 /**< Users loaded successfully */
|
#define MXS_AUTH_LOADUSERS_OK 0 /**< Users loaded successfully */
|
||||||
|
@ -50,7 +50,7 @@ add_library(testcore SHARED testconnections.cpp nodes.cpp mariadb_nodes.cpp maxs
|
|||||||
mariadb_func.cpp get_com_select_insert.cpp maxadmin_operations.cpp big_transaction.cpp
|
mariadb_func.cpp get_com_select_insert.cpp maxadmin_operations.cpp big_transaction.cpp
|
||||||
sql_t1.cpp test_binlog_fnc.cpp get_my_ip.cpp big_load.cpp get_com_select_insert.cpp
|
sql_t1.cpp test_binlog_fnc.cpp get_my_ip.cpp big_load.cpp get_com_select_insert.cpp
|
||||||
different_size.cpp fw_copy_rules maxinfo_func.cpp config_operations.cpp rds_vpc.cpp execute_cmd.cpp
|
different_size.cpp fw_copy_rules maxinfo_func.cpp config_operations.cpp rds_vpc.cpp execute_cmd.cpp
|
||||||
blob_test.cpp keepalived_func.cpp
|
blob_test.cpp keepalived_func.cpp tcp_connection.cpp
|
||||||
# Include the CDC connector in the core library
|
# Include the CDC connector in the core library
|
||||||
${CMAKE_SOURCE_DIR}/../connectors/cdc-connector/cdc_connector.cpp)
|
${CMAKE_SOURCE_DIR}/../connectors/cdc-connector/cdc_connector.cpp)
|
||||||
target_link_libraries(testcore ${MYSQL_CLIENT} ${JANSSON_LIBRARIES} z m pthread ssl dl rt crypto crypt)
|
target_link_libraries(testcore ${MYSQL_CLIENT} ${JANSSON_LIBRARIES} z m pthread ssl dl rt crypto crypt)
|
||||||
@ -921,6 +921,10 @@ add_test_executable_notest(long_sysbench.cpp long_sysbench replication LABELS re
|
|||||||
# test effect of local_address in configuration file
|
# test effect of local_address in configuration file
|
||||||
add_test_executable(local_address.cpp local_address local_address LABELS REPL_BACKEND)
|
add_test_executable(local_address.cpp local_address local_address LABELS REPL_BACKEND)
|
||||||
|
|
||||||
|
# MXS-1628: Security scanner says MaxScale is vulnerable to ancient MySQL vulnerability
|
||||||
|
# https://jira.mariadb.org/browse/MXS-1628
|
||||||
|
add_test_executable(mxs1628_bad_handshake.cpp mxs1628_bad_handshake replication LABELS REPL_BACKEND)
|
||||||
|
|
||||||
configure_file(templates.h.in templates.h @ONLY)
|
configure_file(templates.h.in templates.h @ONLY)
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
|
71
maxscale-system-test/mxs1628_bad_handshake.cpp
Normal file
71
maxscale-system-test/mxs1628_bad_handshake.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include "testconnections.h"
|
||||||
|
#include "tcp_connection.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
TestConnections test(argc, argv);
|
||||||
|
test.set_timeout(30);
|
||||||
|
|
||||||
|
uint32_t caps = 1 | 8 | 512;
|
||||||
|
uint32_t max_packet = 65535;
|
||||||
|
uint8_t charset = 8;
|
||||||
|
std::string username = "username";
|
||||||
|
uint8_t token_len = 20; // SHA1 hash size
|
||||||
|
std::string database = "database";
|
||||||
|
|
||||||
|
// Capabilities, max packet size and client charset
|
||||||
|
std::vector<uint8_t> wbuf;
|
||||||
|
auto it = std::back_inserter(wbuf);
|
||||||
|
|
||||||
|
for (auto a: {(uint8_t)(caps), (uint8_t)(caps >> 8), (uint8_t)(caps >> 16), (uint8_t)(caps >> 24),
|
||||||
|
(uint8_t)(max_packet), (uint8_t)(max_packet >> 8), (uint8_t)(max_packet >> 16), (uint8_t)(max_packet >> 24),
|
||||||
|
charset})
|
||||||
|
{
|
||||||
|
*it++ = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserved filler space
|
||||||
|
std::fill_n(it, 23, 0);
|
||||||
|
|
||||||
|
// Username without terminating null character
|
||||||
|
for (auto a: username)
|
||||||
|
{
|
||||||
|
*it++ = (uint8_t)a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth token length and the token itself
|
||||||
|
*it++ = token_len;
|
||||||
|
std::fill_n(it, token_len, 123);
|
||||||
|
|
||||||
|
// Database without terminating null character
|
||||||
|
for (auto a: database)
|
||||||
|
{
|
||||||
|
*it++ = (uint8_t)a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload length and sequence number
|
||||||
|
uint8_t bufsize = wbuf.size();
|
||||||
|
wbuf.insert(wbuf.begin(), {(uint8_t)(bufsize), (uint8_t)(bufsize >> 8), (uint8_t)(bufsize >> 16), 2});
|
||||||
|
|
||||||
|
|
||||||
|
tcp::Connection conn;
|
||||||
|
conn.connect(test.maxscales->ip(), test.maxscales->rwsplit_port[0]);
|
||||||
|
|
||||||
|
// Read the handshake
|
||||||
|
uint8_t buf[512] = {};
|
||||||
|
conn.read(buf, sizeof(buf));
|
||||||
|
|
||||||
|
// Send the handshake response
|
||||||
|
conn.write(&wbuf[0], wbuf.size());
|
||||||
|
|
||||||
|
// Read MaxScale's response
|
||||||
|
conn.read(buf, sizeof(buf));
|
||||||
|
|
||||||
|
const char response[] = "Bad handshake";
|
||||||
|
test.add_result(memmem(buf, sizeof(buf), response, sizeof(response) - 1) == NULL,
|
||||||
|
"MaxScale should respond with 'Bad handshake'");
|
||||||
|
|
||||||
|
return test.global_result;
|
||||||
|
}
|
89
maxscale-system-test/tcp_connection.cpp
Normal file
89
maxscale-system-test/tcp_connection.cpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#include "tcp_connection.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
static void set_port(struct sockaddr_storage *addr, uint16_t port)
|
||||||
|
{
|
||||||
|
if (addr->ss_family == AF_INET)
|
||||||
|
{
|
||||||
|
struct sockaddr_in *ip = (struct sockaddr_in*)addr;
|
||||||
|
ip->sin_port = htons(port);
|
||||||
|
}
|
||||||
|
else if (addr->ss_family == AF_INET6)
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *ip = (struct sockaddr_in6*)addr;
|
||||||
|
ip->sin6_port = htons(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int open_network_socket(struct sockaddr_storage *addr, const char *host, uint16_t port)
|
||||||
|
{
|
||||||
|
struct addrinfo *ai = NULL, hint = {};
|
||||||
|
int so = -1;
|
||||||
|
hint.ai_socktype = SOCK_STREAM;
|
||||||
|
hint.ai_family = AF_UNSPEC;
|
||||||
|
hint.ai_flags = AI_ALL;
|
||||||
|
|
||||||
|
/* Take the first one */
|
||||||
|
if (getaddrinfo(host, NULL, &hint, &ai) == 0 && ai)
|
||||||
|
{
|
||||||
|
if ((so = socket(ai->ai_family, SOCK_STREAM, 0)) != -1)
|
||||||
|
{
|
||||||
|
memcpy(addr, ai->ai_addr, ai->ai_addrlen);
|
||||||
|
set_port(addr, port);
|
||||||
|
freeaddrinfo(ai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return so;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace tcp
|
||||||
|
{
|
||||||
|
|
||||||
|
Connection::~Connection()
|
||||||
|
{
|
||||||
|
if (m_so != -1)
|
||||||
|
{
|
||||||
|
close(m_so);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Connection::connect(const char* host, uint16_t port)
|
||||||
|
{
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
|
||||||
|
if ((m_so = open_network_socket(&addr, host, port)) != -1)
|
||||||
|
{
|
||||||
|
if (::connect(m_so, (struct sockaddr*)&addr, sizeof(addr)) != 0)
|
||||||
|
{
|
||||||
|
close(m_so);
|
||||||
|
m_so = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_so != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Connection::write(void* buf, size_t size)
|
||||||
|
{
|
||||||
|
return ::write(m_so, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Connection::read(void* buf, size_t size)
|
||||||
|
{
|
||||||
|
return ::read(m_so, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
maxscale-system-test/tcp_connection.h
Normal file
49
maxscale-system-test/tcp_connection.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace tcp
|
||||||
|
{
|
||||||
|
|
||||||
|
// A raw TCP connection
|
||||||
|
class Connection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~Connection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the target server
|
||||||
|
*
|
||||||
|
* @param host Server hostname
|
||||||
|
* @param port Server port
|
||||||
|
*
|
||||||
|
* @return True if connection was successfully created
|
||||||
|
*/
|
||||||
|
bool connect(const char* host, uint16_t port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to socket
|
||||||
|
*
|
||||||
|
* @param buf Buffer to read from
|
||||||
|
* @param size Number of bytes to read from @c buf
|
||||||
|
*
|
||||||
|
* @return Number of written bytes or -1 on error
|
||||||
|
*/
|
||||||
|
int write(void* buf, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from socket
|
||||||
|
*
|
||||||
|
* @param buf Buffer to write to
|
||||||
|
* @param size Size of @c buf
|
||||||
|
*
|
||||||
|
* @return Number of read bytes or -1 on error
|
||||||
|
*/
|
||||||
|
int read(void* buf, size_t size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_so = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -417,11 +417,21 @@ mysql_auth_set_client_data(
|
|||||||
if (client_auth_packet_size > MYSQL_AUTH_PACKET_BASE_SIZE)
|
if (client_auth_packet_size > MYSQL_AUTH_PACKET_BASE_SIZE)
|
||||||
{
|
{
|
||||||
/* Should have a username */
|
/* Should have a username */
|
||||||
char *first_letter_of_username = (char *)(client_auth_packet + MYSQL_AUTH_PACKET_BASE_SIZE);
|
uint8_t* name = client_auth_packet + MYSQL_AUTH_PACKET_BASE_SIZE;
|
||||||
int user_length = strlen(first_letter_of_username);
|
uint8_t* end = client_auth_packet + sizeof(client_auth_packet);
|
||||||
|
int user_length = 0;
|
||||||
|
|
||||||
ss_dassert(client_auth_packet_size > (MYSQL_AUTH_PACKET_BASE_SIZE + user_length)
|
while (name < end && *name)
|
||||||
&& user_length <= MYSQL_USER_MAXLEN);
|
{
|
||||||
|
name++;
|
||||||
|
user_length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == end)
|
||||||
|
{
|
||||||
|
// The name is not null terminated
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (client_auth_packet_size > (MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1))
|
if (client_auth_packet_size > (MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1))
|
||||||
{
|
{
|
||||||
@ -429,14 +439,14 @@ mysql_auth_set_client_data(
|
|||||||
packet_length_used = MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1;
|
packet_length_used = MYSQL_AUTH_PACKET_BASE_SIZE + user_length + 1;
|
||||||
/* We should find an authentication token next */
|
/* We should find an authentication token next */
|
||||||
/* One byte of packet is the length of authentication token */
|
/* One byte of packet is the length of authentication token */
|
||||||
memcpy(&client_data->auth_token_len,
|
client_data->auth_token_len = client_auth_packet[packet_length_used];
|
||||||
client_auth_packet + packet_length_used, 1);
|
|
||||||
|
|
||||||
if (client_auth_packet_size >
|
if (client_auth_packet_size >
|
||||||
(packet_length_used + client_data->auth_token_len))
|
(packet_length_used + client_data->auth_token_len))
|
||||||
{
|
{
|
||||||
/* Packet is large enough for authentication token */
|
client_data->auth_token = (uint8_t*)MXS_MALLOC(client_data->auth_token_len);
|
||||||
if (NULL != (client_data->auth_token = (uint8_t *)MXS_MALLOC(client_data->auth_token_len)))
|
|
||||||
|
if (client_data->auth_token)
|
||||||
{
|
{
|
||||||
/* The extra 1 is for the token length byte, just extracted*/
|
/* The extra 1 is for the token length byte, just extracted*/
|
||||||
memcpy(client_data->auth_token,
|
memcpy(client_data->auth_token,
|
||||||
@ -455,7 +465,12 @@ mysql_auth_set_client_data(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +529,34 @@ int gw_read_client_event(DCB* dcb)
|
|||||||
return return_code;
|
return return_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get length of a null-terminated string
|
||||||
|
*
|
||||||
|
* @param str String to measure
|
||||||
|
* @param len Maximum length to read
|
||||||
|
*
|
||||||
|
* @return Length of @c str or -1 if the string is not null-terminated
|
||||||
|
*/
|
||||||
|
static int get_zstr_len(const char* str, int len)
|
||||||
|
{
|
||||||
|
const char* end = str + len;
|
||||||
|
int slen = 0;
|
||||||
|
|
||||||
|
while (str < end && *str)
|
||||||
|
{
|
||||||
|
str++;
|
||||||
|
slen++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str == end)
|
||||||
|
{
|
||||||
|
// The string is not null terminated
|
||||||
|
slen = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slen;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Store client connection information into the DCB
|
* @brief Store client connection information into the DCB
|
||||||
* @param dcb Client DCB
|
* @param dcb Client DCB
|
||||||
@ -557,22 +585,28 @@ static void store_client_information(DCB *dcb, GWBUF *buffer)
|
|||||||
|
|
||||||
if (len > MYSQL_AUTH_PACKET_BASE_SIZE)
|
if (len > MYSQL_AUTH_PACKET_BASE_SIZE)
|
||||||
{
|
{
|
||||||
strcpy(ses->user, (char*)data + MYSQL_AUTH_PACKET_BASE_SIZE);
|
const char* username = (const char*)data + MYSQL_AUTH_PACKET_BASE_SIZE;
|
||||||
|
int userlen = get_zstr_len(username, len - MYSQL_AUTH_PACKET_BASE_SIZE);
|
||||||
|
|
||||||
|
if (userlen != -1 && (int)sizeof(ses->user) > userlen)
|
||||||
|
{
|
||||||
|
strcpy(ses->user, username);
|
||||||
|
}
|
||||||
|
|
||||||
if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB)
|
if (proto->client_capabilities & GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB)
|
||||||
{
|
{
|
||||||
/** Client supports default database on connect */
|
/** Client is connecting with a default database */
|
||||||
size_t userlen = strlen(ses->user) + 1;
|
|
||||||
|
|
||||||
/** Skip the authentication token, it is handled by the authenticators */
|
|
||||||
uint8_t authlen = data[MYSQL_AUTH_PACKET_BASE_SIZE + userlen];
|
uint8_t authlen = data[MYSQL_AUTH_PACKET_BASE_SIZE + userlen];
|
||||||
|
|
||||||
size_t dboffset = MYSQL_AUTH_PACKET_BASE_SIZE + userlen + authlen + 1;
|
size_t dboffset = MYSQL_AUTH_PACKET_BASE_SIZE + userlen + authlen + 1;
|
||||||
|
|
||||||
if (data[dboffset])
|
if (dboffset < len)
|
||||||
{
|
{
|
||||||
/** Client is connecting with a default database */
|
int dblen = get_zstr_len((const char*)data + dboffset, len - dboffset);
|
||||||
strcpy(ses->db, (char*)data + dboffset);
|
|
||||||
|
if (dblen != -1 && (int)sizeof(ses->db) < dblen)
|
||||||
|
{
|
||||||
|
strcpy(ses->db, (const char*)data + dboffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -661,6 +695,10 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
|
|||||||
{
|
{
|
||||||
auth_val = dcb->authfunc.authenticate(dcb);
|
auth_val = dcb->authfunc.authenticate(dcb);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auth_val = MXS_AUTH_BAD_HANDSHAKE;
|
||||||
|
}
|
||||||
|
|
||||||
MySQLProtocol *protocol = (MySQLProtocol *)dcb->protocol;
|
MySQLProtocol *protocol = (MySQLProtocol *)dcb->protocol;
|
||||||
|
|
||||||
@ -1166,6 +1204,10 @@ mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_number)
|
|||||||
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MXS_AUTH_BAD_HANDSHAKE:
|
||||||
|
modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "08S01", "Bad handshake");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MXS_DEBUG("authentication failed. fd %d, state unrecognized.", dcb->fd);
|
MXS_DEBUG("authentication failed. fd %d, state unrecognized.", dcb->fd);
|
||||||
/** Send error 1045 to client */
|
/** Send error 1045 to client */
|
||||||
|
Reference in New Issue
Block a user