summaryrefslogtreecommitdiff
path: root/libmysql
diff options
context:
space:
mode:
authorRafal Somla <rafal.somla@oracle.com>2011-04-28 21:39:42 +0200
committerRafal Somla <rafal.somla@oracle.com>2011-04-28 21:39:42 +0200
commita6acc73bb18f3a662649c40a12c37063bfae37d6 (patch)
tree117937eb18cc4e988c5738c0d787eaea08cd8b0e /libmysql
parent6d7aceeead15eeefe720a2cbb1fe8f955ceb40fd (diff)
downloadmariadb-git-a6acc73bb18f3a662649c40a12c37063bfae37d6.tar.gz
BUG#11879051: FIRST REPLY LENGTH LIMIT (255) CAN BE VIOLATED
BEFORE: First packet sent by client-side plugin (generated by Windows function InitializeSecurityContext()) could be longer than 255 bytes violating the limitation imposed by authentication protocol. AFTER: Handshake protocol is changed so that if first client's reply is longer than 254 bytes then it is be sent in 2 parts. However, for replies shorter than 255 bytes nothing changes. ADDITIONAL CHANGES: - The generic packet processing loop (Handshake::packet_processing_loop) has been refactored. Communication with the peer has been abstracted into virtual methods read/write_packet() which are implemented in client and server and transparently do the required splitting and gluing of packets. - Make it possible to optionally use dbug library in the plugin. - Add code for testing splitting of long first client reply.
Diffstat (limited to 'libmysql')
-rw-r--r--libmysql/authentication_win/CMakeLists.txt4
-rw-r--r--libmysql/authentication_win/common.h126
-rw-r--r--libmysql/authentication_win/handshake.cc29
-rw-r--r--libmysql/authentication_win/handshake.h17
-rw-r--r--libmysql/authentication_win/handshake_client.cc233
5 files changed, 276 insertions, 133 deletions
diff --git a/libmysql/authentication_win/CMakeLists.txt b/libmysql/authentication_win/CMakeLists.txt
index 82c9dffb06e..80cd14780e6 100644
--- a/libmysql/authentication_win/CMakeLists.txt
+++ b/libmysql/authentication_win/CMakeLists.txt
@@ -18,7 +18,9 @@
#
ADD_DEFINITIONS(-DSECURITY_WIN32)
-ADD_DEFINITIONS(-DDEBUG_ERRROR_LOG) # no error logging in production builds
+ADD_DEFINITIONS(-DDEBUG_ERRROR_LOG) # no error logging in production builds
+ADD_DEFINITIONS(-DWINAUTH_USE_DBUG_LIB) # it is OK to use dbug library in statically
+ # linked plugin
SET(HEADERS common.h handshake.h)
SET(PLUGIN_SOURCES plugin_client.cc handshake_client.cc log_client.cc common.cc handshake.cc)
diff --git a/libmysql/authentication_win/common.h b/libmysql/authentication_win/common.h
index 68f39fe04cc..ff0f7153664 100644
--- a/libmysql/authentication_win/common.h
+++ b/libmysql/authentication_win/common.h
@@ -41,54 +41,65 @@ struct error_log_level
typedef enum {INFO, WARNING, ERROR} type;
};
-#undef DBUG_ASSERT
-#ifndef DBUG_OFF
-#define DBUG_ASSERT(X) assert(X)
+
+/*
+ If DEBUG_ERROR_LOG is defined then error logging happens only
+ in debug-copiled code. Otherwise ERROR_LOG() expands to
+ error_log_print() even in production code. Note that in client
+ plugin, error_log_print() will print nothing if opt_auth_win_clinet_log
+ is 0.
+
+ Note: Macro ERROR_LOG() can use printf-like format string like this:
+
+ ERROR_LOG(Level, ("format string", args));
+
+ The implementation should handle it correctly. Currently it is passed
+ to fprintf() (see error_log_vprint() function).
+*/
+
+extern "C" int opt_auth_win_client_log;
+
+#if defined(DEBUG_ERROR_LOG) && defined(DBUG_OFF)
+#define ERROR_LOG(Level, Msg) do {} while (0)
#else
-#define DBUG_ASSERT(X) do {} while (0)
+#define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg
#endif
-extern "C" int opt_auth_win_client_log;
-/*
- Note1: Double level of indirection in definition of DBUG_PRINT allows to
- temporary redefine or disable DBUG_PRINT macro and then easily return to
- the original definition (in terms of DBUG_PRINT_DO).
+void error_log_vprint(error_log_level::type level,
+ const char *fmt, va_list args);
- Note2: DBUG_PRINT() can use printf-like format string like this:
+template <error_log_level::type Level>
+void error_log_print(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ error_log_vprint(Level, fmt, args);
+ va_end(args);
+}
- DBUG_PRINT(Keyword, ("format string", args));
+typedef char Error_message_buf[1024];
+const char* get_last_error_message(Error_message_buf);
- The implementation should handle it correctly. Currently it is passed
- to fprintf() (see debug_msg() function).
+
+/*
+ Internal implementation of debug message printing which does not use
+ dbug library. This is invoked via macro:
+
+ DBUG_PRINT_DO(Keyword, ("format string", args));
+
+ This is supposed to be used as an implementation of DBUG_PRINT() macro,
+ unless the dbug library implementation is used or debug messages are disabled.
*/
#ifndef DBUG_OFF
+
#define DBUG_PRINT_DO(Keyword, Msg) \
do { \
if (2 > opt_auth_win_client_log) break; \
fprintf(stderr, "winauth: %s: ", Keyword); \
debug_msg Msg; \
} while (0)
-#else
-#define DBUG_PRINT_DO(K, M) do {} while (0)
-#endif
-
-#undef DBUG_PRINT
-#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg)
-
-/*
- If DEBUG_ERROR_LOG is defined then error logging happens only
- in debug-copiled code. Otherwise ERROR_LOG() expands to
- error_log_print() even in production code. Note that in client
- plugin, error_log_print() will print nothing if opt_auth_win_clinet_log
- is 0.
-*/
-#if defined(DEBUG_ERROR_LOG) && defined(DBUG_OFF)
-#define ERROR_LOG(Level, Msg) do {} while (0)
-#else
-#define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg
-#endif
inline
void debug_msg(const char *fmt, ...)
@@ -101,21 +112,38 @@ void debug_msg(const char *fmt, ...)
va_end(args);
}
+#else
+#define DBUG_PRINT_DO(K, M) do {} while (0)
+#endif
-void error_log_vprint(error_log_level::type level,
- const char *fmt, va_list args);
-template <error_log_level::type Level>
-void error_log_print(const char *fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- error_log_vprint(Level, fmt, args);
- va_end(args);
-}
+#ifndef WINAUTH_USE_DBUG_LIB
-typedef char Error_message_buf[1024];
-const char* get_last_error_message(Error_message_buf);
+#undef DBUG_PRINT
+#define DBUG_PRINT(Keyword, Msg) DBUG_PRINT_DO(Keyword, Msg)
+
+/*
+ Redefine few more debug macros to make sure that no symbols from
+ dbug library are used.
+*/
+
+#undef DBUG_ENTER
+#define DBUG_ENTER(X) do {} while (0)
+
+#undef DBUG_RETURN
+#define DBUG_RETURN(X) return (X)
+
+#undef DBUG_ASSERT
+#ifndef DBUG_OFF
+#define DBUG_ASSERT(X) assert (X)
+#else
+#define DBUG_ASSERT(X) do {} while (0)
+#endif
+
+#undef DBUG_DUMP
+#define DBUG_DUMP(A,B,C) do {} while (0)
+
+#endif
/** Blob class *************************************************************/
@@ -158,15 +186,21 @@ public:
return m_len;
}
- byte operator[](unsigned pos) const
+ byte& operator[](unsigned pos) const
{
- return pos < len() ? m_ptr[pos] : 0x00;
+ static byte out_of_range= 0; // alas, no exceptions...
+ return pos < len() ? m_ptr[pos] : out_of_range;
}
bool is_null() const
{
return m_ptr == NULL;
}
+
+ void trim(size_t l)
+ {
+ m_len= l;
+ }
};
diff --git a/libmysql/authentication_win/handshake.cc b/libmysql/authentication_win/handshake.cc
index 4b33349d0f4..ec665af9ef9 100644
--- a/libmysql/authentication_win/handshake.cc
+++ b/libmysql/authentication_win/handshake.cc
@@ -90,17 +90,19 @@ Handshake::~Handshake()
@note In case of error, appropriate error message is logged.
*/
-int Handshake::packet_processing_loop(Connection &con)
+int Handshake::packet_processing_loop()
{
- unsigned round= 1;
+ m_round= 0;
do {
+ ++m_round;
// Read packet send by the peer
+
DBUG_PRINT("info", ("Waiting for packet"));
- Blob packet= con.read();
- if (con.error() || packet.is_null())
+ Blob packet= read_packet();
+ if (error())
{
- ERROR_LOG(ERROR, ("Error reading packet in round %d", round));
+ ERROR_LOG(ERROR, ("Error reading packet in round %d", m_round));
return 1;
}
DBUG_PRINT("info", ("Got packet of length %d", packet.len()));
@@ -113,7 +115,7 @@ int Handshake::packet_processing_loop(Connection &con)
if (error())
{
- ERROR_LOG(ERROR, ("Error processing packet in round %d", round));
+ ERROR_LOG(ERROR, ("Error processing packet in round %d", m_round));
return 1;
}
@@ -124,14 +126,13 @@ int Handshake::packet_processing_loop(Connection &con)
if (!new_data.is_null())
{
- ++round;
- DBUG_PRINT("info", ("Round %d started", round));
+ DBUG_PRINT("info", ("Round %d started", m_round));
DBUG_PRINT("info", ("Sending packet of length %d", new_data.len()));
- int ret= con.write(new_data);
+ int ret= write_packet(new_data);
if (ret)
{
- ERROR_LOG(ERROR, ("Error writing packet in round %d", round));
+ ERROR_LOG(ERROR, ("Error writing packet in round %d", m_round));
return 1;
}
DBUG_PRINT("info", ("Data sent"));
@@ -139,7 +140,7 @@ int Handshake::packet_processing_loop(Connection &con)
else if (!is_complete())
{
ERROR_LOG(ERROR, ("No data to send in round %d"
- " but handshake is not complete", round));
+ " but handshake is not complete", m_round));
return 1;
}
@@ -148,16 +149,16 @@ int Handshake::packet_processing_loop(Connection &con)
too many rounds.
*/
- if (round > MAX_HANDSHAKE_ROUNDS)
+ if (m_round > MAX_HANDSHAKE_ROUNDS)
{
ERROR_LOG(ERROR, ("Authentication handshake could not be completed"
- " after %d rounds", round));
+ " after %d rounds", m_round));
return 1;
}
} while(!is_complete());
- ERROR_LOG(INFO, ("Handshake completed after %d rounds", round));
+ ERROR_LOG(INFO, ("Handshake completed after %d rounds", m_round));
return 0;
}
diff --git a/libmysql/authentication_win/handshake.h b/libmysql/authentication_win/handshake.h
index bb88510a7f8..5292948b583 100644
--- a/libmysql/authentication_win/handshake.h
+++ b/libmysql/authentication_win/handshake.h
@@ -100,7 +100,7 @@ public:
Handshake(const char *ssp, side_t side);
virtual ~Handshake();
- int Handshake::packet_processing_loop(Connection &con);
+ int Handshake::packet_processing_loop();
bool virtual is_complete() const
{
@@ -126,6 +126,13 @@ protected:
/// Stores attributes of the created security context.
ULONG m_atts;
+ /**
+ Round of the handshake (starting from round 1). One round
+ consist of reading packet from the other side, processing it and
+ optionally sending a reply (see @c packet_processing_loop()).
+ */
+ unsigned int m_round;
+
/// If non-zero, stores error code of the last failed operation.
int m_error;
@@ -152,7 +159,13 @@ protected:
@return A blob with data to be sent to the other end or null blob if
no more data needs to be exchanged.
*/
- virtual Blob process_data(const Blob &data)= 0;
+ virtual Blob process_data(const Blob &data) =0;
+
+ /// Read packet from the other end.
+ virtual Blob read_packet() =0;
+
+ /// Write packet to the other end.
+ virtual int write_packet(Blob &data) =0;
#ifndef DBUG_OFF
diff --git a/libmysql/authentication_win/handshake_client.cc b/libmysql/authentication_win/handshake_client.cc
index e42f055da70..7e89fc92ae7 100644
--- a/libmysql/authentication_win/handshake_client.cc
+++ b/libmysql/authentication_win/handshake_client.cc
@@ -33,21 +33,27 @@ class Handshake_client: public Handshake
/// Buffer for storing service name obtained from server.
SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH];
+ Connection &m_con;
+
public:
- Handshake_client(const char *target, size_t len);
+ Handshake_client(Connection &con, const char *target, size_t len);
~Handshake_client();
Blob first_packet();
Blob process_data(const Blob&);
+
+ Blob read_packet();
+ int write_packet(Blob &data);
};
/**
Create authentication handshake context for client.
- @param target name of the target service with which we will authenticate
- (can be NULL if not used)
+ @param con connection for communication with the peer
+ @param target name of the target service with which we will authenticate
+ (can be NULL if not used)
Some security packages (like Kerberos) require providing explicit name
of the service with which a client wants to authenticate. The server-side
@@ -55,8 +61,9 @@ public:
(see @c win_auth_handshake_{server,client}() functions).
*/
-Handshake_client::Handshake_client(const char *target, size_t len)
-: Handshake(SSP_NAME, CLIENT), m_service_name(NULL)
+Handshake_client::Handshake_client(Connection &con,
+ const char *target, size_t len)
+: Handshake(SSP_NAME, CLIENT), m_service_name(NULL), m_con(con)
{
if (!target || 0 == len)
return;
@@ -88,43 +95,97 @@ Handshake_client::~Handshake_client()
}
-/**
- Generate first packet to be sent to the server during packet exchange.
+Blob Handshake_client::read_packet()
+{
+ /*
+ We do a fake read in the first round because first
+ packet from the server containing UPN must be read
+ before the handshake context is created and the packet
+ processing loop starts. We return an empty blob here
+ and process_data() function will ignore it.
+ */
+ if (m_round == 1)
+ return Blob();
+
+ // Otherwise we read packet from the connection.
+
+ Blob packet= m_con.read();
+ m_error= m_con.error();
+ if (!m_error && packet.is_null())
+ m_error= true; // (no specific error code assigned)
+
+ if (m_error)
+ return Blob();
+
+ DBUG_PRINT("dump", ("Got the following bytes"));
+ DBUG_DUMP("dump", packet.ptr(), packet.len());
+ return packet;
+}
- This first packet should contain some data. In case of error a null blob
- is returned and @c error() gives non-zero error code.
- @return Data to be sent in the first packet or null blob in case of error.
-*/
-Blob Handshake_client::first_packet()
+int Handshake_client::write_packet(Blob &data)
{
- SECURITY_STATUS ret;
+ /*
+ Length of the first data payload send by client authentication plugin is
+ limited to 255 bytes (because it is wrapped inside client authentication
+ packet and is length-encoded with 1 byte for the length).
+
+ If the data payload is longer than 254 bytes, then it is sent in two parts:
+ first part of length 255 will be embedded in the authentication packet,
+ second part will be sent in the following packet. Byte 255 of the first
+ part contains information about the total length of the payload. It is a
+ number of blocks of size 512 bytes which is sufficient to store the
+ combined packets.
+
+ Server's logic for reading first client's payload is as follows
+ (see Handshake_server::read_packet()):
+ 1. Read data from the authentication packet, if it is shorter than 255 bytes
+ then that is all data sent by client.
+ 2. If there is 255 bytes of data in the authentication packet, read another
+ packet and append it to the data, skipping byte 255 of the first packet
+ which can be used to allocate buffer of appropriate size.
+ */
- m_output.free();
+ size_t len2= 0; // length of the second part of first data payload
+ byte saved_byte; // for saving byte 255 in which data length is stored
- ret= InitializeSecurityContextW(
- &m_cred,
- NULL, // partial context
- m_service_name, // service name
- ASC_REQ_ALLOCATE_MEMORY, // requested attributes
- 0, // reserved
- SECURITY_NETWORK_DREP, // data representation
- NULL, // input data
- 0, // reserved
- &m_sctx, // context
- &m_output, // output data
- &m_atts, // attributes
- &m_expire); // expire date
+ if (m_round == 1 && data.len() > 254)
+ {
+ len2= data.len() - 254;
+ DBUG_PRINT("info", ("Splitting first packet of length %lu"
+ ", %lu bytes will be sent in a second part",
+ data.len(), len2));
+ /*
+ Store in byte 255 the number of 512b blocks that are needed to
+ keep all the data.
+ */
+ unsigned block_count= data.len()/512 + ((data.len() % 512) ? 1 : 0);
+ DBUG_ASSERT(block_count < (unsigned)0x100);
+ saved_byte= data[254];
+ data[254] = block_count;
- if (process_result(ret))
+ data.trim(255);
+ }
+
+ DBUG_PRINT("dump", ("Sending the following data"));
+ DBUG_DUMP("dump", data.ptr(), data.len());
+ int ret= m_con.write(data);
+
+ if (ret)
+ return ret;
+
+ // Write second part if it is present.
+ if (len2)
{
- DBUG_PRINT("error",
- ("InitializeSecurityContext() failed with error %X", ret));
- return Blob();
+ data[254]= saved_byte;
+ Blob data2(data.ptr() + 254, len2);
+ DBUG_PRINT("info", ("Sending second part of data"));
+ DBUG_DUMP("info", data2.ptr(), data2.len());
+ ret= m_con.write(data2);
}
- return m_output.as_blob();
+ return ret;
}
@@ -139,12 +200,66 @@ Blob Handshake_client::first_packet()
empty blob is returned and @c is_complete() is @c true. In case of error
an empty blob is returned and @c error() gives non-zero error code.
+ When invoked for the first time (in the first round of the handshake)
+ there is no data from the server (data blob is null) and the intial
+ packet is generated without an input.
+
@return Data to be sent to the server next or null blob if no more data
needs to be exchanged or in case of error.
*/
Blob Handshake_client::process_data(const Blob &data)
{
+#if !defined(DBUG_OFF) && defined(WINAUTH_USE_DBUG_LIB)
+ /*
+ Code for testing the logic for sending the first client payload.
+
+ A fake data of length given by environment variable TEST_PACKET_LENGTH
+ (or default 255 bytes) is sent to the server. First 2 bytes of the
+ payload contain its total length (LSB first). The length of test data
+ is limited to 2048 bytes.
+
+ Upon receiving test data, server will check that data is correct and
+ refuse connection. If server detects data errors it will crash on
+ assertion.
+
+ This code is executed if debug flag "winauth_first_packet_test" is
+ set, e.g. using client option:
+
+ --debug="d,winauth_first_packet_test"
+
+ The same debug flag must be enabled in the server, e.g. using
+ statement:
+
+ SET GLOBAL debug= '+d,winauth_first_packet_test';
+ */
+
+ static byte test_buf[2048];
+
+ if (m_round == 1
+ && DBUG_EVALUATE_IF("winauth_first_packet_test", true, false))
+ {
+ const char *env= getenv("TEST_PACKET_LENGTH");
+ size_t len= env ? atoi(env) : 0;
+ if (!len)
+ len= 255;
+ if (len > sizeof(test_buf))
+ len= sizeof(test_buf);
+
+ // Store data length in first 2 bytes.
+ byte *ptr= test_buf;
+ *ptr++= len & 0xFF;
+ *ptr++= len >> 8;
+
+ // Fill remaining bytes with known values.
+ for (byte b= 0; ptr < test_buf + len; ++ptr, ++b)
+ *ptr= b;
+
+ return Blob(test_buf, len);
+ };
+
+#endif
+
Security_buffer input(data);
SECURITY_STATUS ret;
@@ -152,12 +267,12 @@ Blob Handshake_client::process_data(const Blob &data)
ret= InitializeSecurityContextW(
&m_cred,
- &m_sctx, // partial context
+ m_round == 1 ? NULL : &m_sctx, // partial context
m_service_name, // service name
ASC_REQ_ALLOCATE_MEMORY, // requested attributes
0, // reserved
SECURITY_NETWORK_DREP, // data representation
- &input, // input data
+ m_round == 1 ? NULL : &input, // input data
0, // reserved
&m_sctx, // context
&m_output, // output data
@@ -198,6 +313,8 @@ Blob Handshake_client::process_data(const Blob &data)
int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
+ DBUG_ENTER("win_auth_handshake_client");
+
/*
Check if we should enable logging.
*/
@@ -224,62 +341,38 @@ int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
// Read initial packet from server containing service name.
- int ret;
Blob service_name= con.read();
if (con.error() || service_name.is_null())
{
ERROR_LOG(ERROR, ("Error reading initial packet"));
- return CR_ERROR;
+ DBUG_RETURN(CR_ERROR);
}
DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len()));
- // Create authentication handsake context using the given service name.
+ // Create authentication handshake context using the given service name.
- Handshake_client hndshk(service_name[0] ? (char *)service_name.ptr() : NULL,
+ Handshake_client hndshk(con,
+ service_name[0] ? (char *)service_name.ptr() : NULL,
service_name.len());
if (hndshk.error())
{
ERROR_LOG(ERROR, ("Could not create authentication handshake context"));
- return CR_ERROR;
- }
-
- /*
- The following packet exchange always starts with a packet sent by
- the client. Send this first packet now.
- */
-
- {
- Blob packet= hndshk.first_packet();
- if (hndshk.error() || packet.is_null())
- {
- ERROR_LOG(ERROR, ("Could not generate first packet"));
- return CR_ERROR;
- }
- DBUG_PRINT("info", ("Sending first packet of length %d", packet.len()));
-
- ret= con.write(packet);
- if (ret)
- {
- ERROR_LOG(ERROR, ("Error writing first packet"));
- return CR_ERROR;
- }
- DBUG_PRINT("info", ("First packet sent"));
+ DBUG_RETURN(CR_ERROR);
}
DBUG_ASSERT(!hndshk.error());
/*
- If handshake is not yet complete and client expects a reply,
- read and process packets from server until handshake is complete.
+ Read and process packets from server until handshake is complete.
+ Note that the first read from server is dummy
+ (see Handshake_client::read_packet()) as we already have read the
+ first packet to establish service name.
*/
- if (!hndshk.is_complete())
- {
- if (hndshk.packet_processing_loop(con))
- return CR_ERROR;
- }
+ if (hndshk.packet_processing_loop())
+ DBUG_RETURN(CR_ERROR);
DBUG_ASSERT(!hndshk.error() && hndshk.is_complete());
- return CR_OK;
+ DBUG_RETURN(CR_OK);
}