Commit 4eebae08 authored by Rafal Somla's avatar Rafal Somla

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.
parent c8e48ac3
...@@ -18,7 +18,9 @@ ...@@ -18,7 +18,9 @@
# #
ADD_DEFINITIONS(-DSECURITY_WIN32) 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(HEADERS common.h handshake.h)
SET(PLUGIN_SOURCES plugin_client.cc handshake_client.cc log_client.cc common.cc handshake.cc) SET(PLUGIN_SOURCES plugin_client.cc handshake_client.cc log_client.cc common.cc handshake.cc)
......
...@@ -41,54 +41,65 @@ struct error_log_level ...@@ -41,54 +41,65 @@ struct error_log_level
typedef enum {INFO, WARNING, ERROR} type; 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 #else
#define DBUG_ASSERT(X) do {} while (0) #define ERROR_LOG(Level, Msg) error_log_print< error_log_level::Level > Msg
#endif #endif
extern "C" int opt_auth_win_client_log;
/* void error_log_vprint(error_log_level::type level,
Note1: Double level of indirection in definition of DBUG_PRINT allows to const char *fmt, va_list args);
temporary redefine or disable DBUG_PRINT macro and then easily return to
the original definition (in terms of DBUG_PRINT_DO).
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 #ifndef DBUG_OFF
#define DBUG_PRINT_DO(Keyword, Msg) \ #define DBUG_PRINT_DO(Keyword, Msg) \
do { \ do { \
if (2 > opt_auth_win_client_log) break; \ if (2 > opt_auth_win_client_log) break; \
fprintf(stderr, "winauth: %s: ", Keyword); \ fprintf(stderr, "winauth: %s: ", Keyword); \
debug_msg Msg; \ debug_msg Msg; \
} while (0) } 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 inline
void debug_msg(const char *fmt, ...) void debug_msg(const char *fmt, ...)
...@@ -101,21 +112,38 @@ void debug_msg(const char *fmt, ...) ...@@ -101,21 +112,38 @@ void debug_msg(const char *fmt, ...)
va_end(args); 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> #ifndef WINAUTH_USE_DBUG_LIB
void error_log_print(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
error_log_vprint(Level, fmt, args);
va_end(args);
}
typedef char Error_message_buf[1024]; #undef DBUG_PRINT
const char* get_last_error_message(Error_message_buf); #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 *************************************************************/ /** Blob class *************************************************************/
...@@ -158,15 +186,21 @@ public: ...@@ -158,15 +186,21 @@ public:
return m_len; 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 bool is_null() const
{ {
return m_ptr == NULL; return m_ptr == NULL;
} }
void trim(size_t l)
{
m_len= l;
}
}; };
......
...@@ -90,17 +90,19 @@ Handshake::~Handshake() ...@@ -90,17 +90,19 @@ Handshake::~Handshake()
@note In case of error, appropriate error message is logged. @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 { do {
++m_round;
// Read packet send by the peer // Read packet send by the peer
DBUG_PRINT("info", ("Waiting for packet")); DBUG_PRINT("info", ("Waiting for packet"));
Blob packet= con.read(); Blob packet= read_packet();
if (con.error() || packet.is_null()) 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; return 1;
} }
DBUG_PRINT("info", ("Got packet of length %d", packet.len())); DBUG_PRINT("info", ("Got packet of length %d", packet.len()));
...@@ -113,7 +115,7 @@ int Handshake::packet_processing_loop(Connection &con) ...@@ -113,7 +115,7 @@ int Handshake::packet_processing_loop(Connection &con)
if (error()) 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; return 1;
} }
...@@ -124,14 +126,13 @@ int Handshake::packet_processing_loop(Connection &con) ...@@ -124,14 +126,13 @@ int Handshake::packet_processing_loop(Connection &con)
if (!new_data.is_null()) if (!new_data.is_null())
{ {
++round; DBUG_PRINT("info", ("Round %d started", m_round));
DBUG_PRINT("info", ("Round %d started", round));
DBUG_PRINT("info", ("Sending packet of length %d", new_data.len())); 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) 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; return 1;
} }
DBUG_PRINT("info", ("Data sent")); DBUG_PRINT("info", ("Data sent"));
...@@ -139,7 +140,7 @@ int Handshake::packet_processing_loop(Connection &con) ...@@ -139,7 +140,7 @@ int Handshake::packet_processing_loop(Connection &con)
else if (!is_complete()) else if (!is_complete())
{ {
ERROR_LOG(ERROR, ("No data to send in round %d" 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; return 1;
} }
...@@ -148,16 +149,16 @@ int Handshake::packet_processing_loop(Connection &con) ...@@ -148,16 +149,16 @@ int Handshake::packet_processing_loop(Connection &con)
too many rounds. too many rounds.
*/ */
if (round > MAX_HANDSHAKE_ROUNDS) if (m_round > MAX_HANDSHAKE_ROUNDS)
{ {
ERROR_LOG(ERROR, ("Authentication handshake could not be completed" ERROR_LOG(ERROR, ("Authentication handshake could not be completed"
" after %d rounds", round)); " after %d rounds", m_round));
return 1; return 1;
} }
} while(!is_complete()); } while(!is_complete());
ERROR_LOG(INFO, ("Handshake completed after %d rounds", round)); ERROR_LOG(INFO, ("Handshake completed after %d rounds", m_round));
return 0; return 0;
} }
......
...@@ -100,7 +100,7 @@ public: ...@@ -100,7 +100,7 @@ public:
Handshake(const char *ssp, side_t side); Handshake(const char *ssp, side_t side);
virtual ~Handshake(); virtual ~Handshake();
int Handshake::packet_processing_loop(Connection &con); int Handshake::packet_processing_loop();
bool virtual is_complete() const bool virtual is_complete() const
{ {
...@@ -126,6 +126,13 @@ protected: ...@@ -126,6 +126,13 @@ protected:
/// Stores attributes of the created security context. /// Stores attributes of the created security context.
ULONG m_atts; 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. /// If non-zero, stores error code of the last failed operation.
int m_error; int m_error;
...@@ -152,7 +159,13 @@ protected: ...@@ -152,7 +159,13 @@ protected:
@return A blob with data to be sent to the other end or null blob if @return A blob with data to be sent to the other end or null blob if
no more data needs to be exchanged. 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 #ifndef DBUG_OFF
......
...@@ -33,21 +33,27 @@ class Handshake_client: public Handshake ...@@ -33,21 +33,27 @@ class Handshake_client: public Handshake
/// Buffer for storing service name obtained from server. /// Buffer for storing service name obtained from server.
SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH]; SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH];
Connection &m_con;
public: public:
Handshake_client(const char *target, size_t len); Handshake_client(Connection &con, const char *target, size_t len);
~Handshake_client(); ~Handshake_client();
Blob first_packet(); Blob first_packet();
Blob process_data(const Blob&); Blob process_data(const Blob&);
Blob read_packet();
int write_packet(Blob &data);
}; };
/** /**
Create authentication handshake context for client. Create authentication handshake context for client.
@param target name of the target service with which we will authenticate @param con connection for communication with the peer
(can be NULL if not used) @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 Some security packages (like Kerberos) require providing explicit name
of the service with which a client wants to authenticate. The server-side of the service with which a client wants to authenticate. The server-side
...@@ -55,8 +61,9 @@ public: ...@@ -55,8 +61,9 @@ public:
(see @c win_auth_handshake_{server,client}() functions). (see @c win_auth_handshake_{server,client}() functions).
*/ */
Handshake_client::Handshake_client(const char *target, size_t len) Handshake_client::Handshake_client(Connection &con,
: Handshake(SSP_NAME, CLIENT), m_service_name(NULL) const char *target, size_t len)
: Handshake(SSP_NAME, CLIENT), m_service_name(NULL), m_con(con)
{ {
if (!target || 0 == len) if (!target || 0 == len)
return; return;
...@@ -88,43 +95,97 @@ Handshake_client::~Handshake_client() ...@@ -88,43 +95,97 @@ Handshake_client::~Handshake_client()
} }
/** Blob Handshake_client::read_packet()
Generate first packet to be sent to the server during packet exchange. {
/*
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( if (m_round == 1 && data.len() > 254)
&m_cred, {
NULL, // partial context len2= data.len() - 254;
m_service_name, // service name DBUG_PRINT("info", ("Splitting first packet of length %lu"
ASC_REQ_ALLOCATE_MEMORY, // requested attributes ", %lu bytes will be sent in a second part",
0, // reserved data.len(), len2));
SECURITY_NETWORK_DREP, // data representation /*
NULL, // input data Store in byte 255 the number of 512b blocks that are needed to
0, // reserved keep all the data.
&m_sctx, // context */
&m_output, // output data unsigned block_count= data.len()/512 + ((data.len() % 512) ? 1 : 0);
&m_atts, // attributes DBUG_ASSERT(block_count < (unsigned)0x100);
&m_expire); // expire date 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", data[254]= saved_byte;
("InitializeSecurityContext() failed with error %X", ret)); Blob data2(data.ptr() + 254, len2);
return Blob(); 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() ...@@ -139,12 +200,66 @@ Blob Handshake_client::first_packet()
empty blob is returned and @c is_complete() is @c true. In case of error 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. 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 @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. needs to be exchanged or in case of error.
*/ */
Blob Handshake_client::process_data(const Blob &data) 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_buffer input(data);
SECURITY_STATUS ret; SECURITY_STATUS ret;
...@@ -152,12 +267,12 @@ Blob Handshake_client::process_data(const Blob &data) ...@@ -152,12 +267,12 @@ Blob Handshake_client::process_data(const Blob &data)
ret= InitializeSecurityContextW( ret= InitializeSecurityContextW(
&m_cred, &m_cred,
&m_sctx, // partial context m_round == 1 ? NULL : &m_sctx, // partial context
m_service_name, // service name m_service_name, // service name
ASC_REQ_ALLOCATE_MEMORY, // requested attributes ASC_REQ_ALLOCATE_MEMORY, // requested attributes
0, // reserved 0, // reserved
SECURITY_NETWORK_DREP, // data representation SECURITY_NETWORK_DREP, // data representation
&input, // input data m_round == 1 ? NULL : &input, // input data
0, // reserved 0, // reserved
&m_sctx, // context &m_sctx, // context
&m_output, // output data &m_output, // output data
...@@ -198,6 +313,8 @@ Blob Handshake_client::process_data(const Blob &data) ...@@ -198,6 +313,8 @@ Blob Handshake_client::process_data(const Blob &data)
int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{ {
DBUG_ENTER("win_auth_handshake_client");
/* /*
Check if we should enable logging. Check if we should enable logging.
*/ */
...@@ -224,62 +341,38 @@ int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) ...@@ -224,62 +341,38 @@ int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
// Read initial packet from server containing service name. // Read initial packet from server containing service name.
int ret;
Blob service_name= con.read(); Blob service_name= con.read();
if (con.error() || service_name.is_null()) if (con.error() || service_name.is_null())
{ {
ERROR_LOG(ERROR, ("Error reading initial packet")); 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())); 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()); service_name.len());
if (hndshk.error()) if (hndshk.error())
{ {
ERROR_LOG(ERROR, ("Could not create authentication handshake context")); ERROR_LOG(ERROR, ("Could not create authentication handshake context"));
return CR_ERROR; DBUG_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_ASSERT(!hndshk.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())
{ DBUG_RETURN(CR_ERROR);
if (hndshk.packet_processing_loop(con))
return CR_ERROR;
}
DBUG_ASSERT(!hndshk.error() && hndshk.is_complete()); DBUG_ASSERT(!hndshk.error() && hndshk.is_complete());
return CR_OK; DBUG_RETURN(CR_OK);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment