Merge pull request #1347 from p12tic/enforce-max-message-length

Enforce max message length [SECURITY VULNERABILITY CVE-2021-42076]
This commit is contained in:
Povilas Kanapickas 2021-11-01 05:56:38 +02:00 committed by GitHub
commit 00e182d22e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 105 additions and 30 deletions

View File

@ -0,0 +1,6 @@
SECURITY ISSUE
Barrier will now enforce a maximum length of input messages (fixes CVE-2021-42076).
Previously it was possible for a malicious client or server to send excessive length messages
leading to denial of service by resource exhaustion.

View File

@ -17,6 +17,7 @@
*/ */
#include "barrier/PacketStreamFilter.h" #include "barrier/PacketStreamFilter.h"
#include "barrier/protocol_types.h"
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#include "mt/Lock.h" #include "mt/Lock.h"
#include "base/TMethodEventJob.h" #include "base/TMethodEventJob.h"
@ -133,8 +134,7 @@ PacketStreamFilter::isReadyNoLock() const
return (m_size != 0 && m_buffer.getSize() >= m_size); return (m_size != 0 && m_buffer.getSize() >= m_size);
} }
void bool PacketStreamFilter::readPacketSize()
PacketStreamFilter::readPacketSize()
{ {
// note -- m_mutex must be locked on entry // note -- m_mutex must be locked on entry
@ -146,7 +146,13 @@ PacketStreamFilter::readPacketSize()
((UInt32)buffer[1] << 16) | ((UInt32)buffer[1] << 16) |
((UInt32)buffer[2] << 8) | ((UInt32)buffer[2] << 8) |
(UInt32)buffer[3]; (UInt32)buffer[3];
if (m_size > PROTOCOL_MAX_MESSAGE_LENGTH) {
m_events->addEvent(Event(m_events->forIStream().inputFormatError(), getEventTarget()));
return false;
} }
}
return true;
} }
bool bool
@ -160,12 +166,16 @@ PacketStreamFilter::readMore()
UInt32 n = getStream()->read(buffer, sizeof(buffer)); UInt32 n = getStream()->read(buffer, sizeof(buffer));
while (n > 0) { while (n > 0) {
m_buffer.write(buffer, n); m_buffer.write(buffer, n);
n = getStream()->read(buffer, sizeof(buffer));
// if we don't yet have the next packet size then get it, if possible.
// Note that we can't wait for whole pending data to arrive because it may be huge in
// case of malicious or erroneous peer.
if (!readPacketSize()) {
break;
} }
// if we don't yet have the next packet size then get it, n = getStream()->read(buffer, sizeof(buffer));
// if possible. }
readPacketSize();
// note if we now have a whole packet // note if we now have a whole packet
bool isReady = isReadyNoLock(); bool isReady = isReadyNoLock();

View File

@ -47,7 +47,9 @@ protected:
private: private:
bool isReadyNoLock() const; bool isReadyNoLock() const;
void readPacketSize();
// returns false on erroneous packet size
bool readPacketSize();
bool readMore(); bool readMore();
private: private:

View File

@ -19,6 +19,8 @@
#include "barrier/ProtocolUtil.h" #include "barrier/ProtocolUtil.h"
#include "io/IStream.h" #include "io/IStream.h"
#include "base/Log.h" #include "base/Log.h"
#include "barrier/protocol_types.h"
#include "barrier/XBarrier.h"
#include "common/stdvector.h" #include "common/stdvector.h"
#include "base/String.h" #include "base/String.h"
@ -159,6 +161,10 @@ ProtocolUtil::vreadf(barrier::IStream* stream, const char* fmt, va_list args)
(static_cast<UInt32>(buffer[2]) << 8) | (static_cast<UInt32>(buffer[2]) << 8) |
static_cast<UInt32>(buffer[3]); static_cast<UInt32>(buffer[3]);
if (n > PROTOCOL_MAX_LIST_LENGTH) {
throw XBadClient("Too long message received");
}
// convert it // convert it
void* v = va_arg(args, void*); void* v = va_arg(args, void*);
switch (len) { switch (len) {
@ -211,6 +217,10 @@ ProtocolUtil::vreadf(barrier::IStream* stream, const char* fmt, va_list args)
(static_cast<UInt32>(buffer[2]) << 8) | (static_cast<UInt32>(buffer[2]) << 8) |
static_cast<UInt32>(buffer[3]); static_cast<UInt32>(buffer[3]);
if (len > PROTOCOL_MAX_STRING_LENGTH) {
throw XBadClient("Too long message received");
}
// use a fixed size buffer if its big enough // use a fixed size buffer if its big enough
const bool useFixed = (len <= sizeof(buffer)); const bool useFixed = (len <= sizeof(buffer));

View File

@ -20,6 +20,8 @@
#include "base/EventTypes.h" #include "base/EventTypes.h"
#include <cstdint>
// protocol version number // protocol version number
// 1.0: initial protocol // 1.0: initial protocol
// 1.1: adds KeyCode to key press, release, and repeat // 1.1: adds KeyCode to key press, release, and repeat
@ -51,6 +53,12 @@ static const double kKeepAlivesUntilDeath = 3.0;
static const double kHeartRate = -1.0; static const double kHeartRate = -1.0;
static const double kHeartBeatsUntilDeath = 3.0; static const double kHeartBeatsUntilDeath = 3.0;
// Messages of very large size indicate a likely protocol error. We don't parse such messages and
// drop connection instead. Note that e.g. the clipboard messages are already limited to 32kB.
static constexpr std::uint32_t PROTOCOL_MAX_MESSAGE_LENGTH = 4 * 1024 * 1024;
static constexpr std::uint32_t PROTOCOL_MAX_LIST_LENGTH = 1024 * 1024;
static constexpr std::uint32_t PROTOCOL_MAX_STRING_LENGTH = 1024 * 1024;
// direction constants // direction constants
enum EDirection { enum EDirection {
kNoDirection, kNoDirection,

View File

@ -56,6 +56,7 @@ REGISTER_EVENT(IStream, outputFlushed)
REGISTER_EVENT(IStream, outputError) REGISTER_EVENT(IStream, outputError)
REGISTER_EVENT(IStream, inputShutdown) REGISTER_EVENT(IStream, inputShutdown)
REGISTER_EVENT(IStream, outputShutdown) REGISTER_EVENT(IStream, outputShutdown)
REGISTER_EVENT(IStream, inputFormatError)
// //
// IpcClient // IpcClient

View File

@ -133,6 +133,11 @@ public:
*/ */
Event::Type outputShutdown(); Event::Type outputShutdown();
/** Get input format error event type
This is sent when a stream receives an irrecoverable input format error.
*/
Event::Type inputFormatError();
//@} //@}
private: private:
@ -141,6 +146,7 @@ private:
Event::Type m_outputError; Event::Type m_outputError;
Event::Type m_inputShutdown; Event::Type m_inputShutdown;
Event::Type m_outputShutdown; Event::Type m_outputShutdown;
Event::Type m_inputFormatError;
}; };
class IpcClientEvents : public EventTypes { class IpcClientEvents : public EventTypes {

View File

@ -26,6 +26,7 @@
#include "barrier/ProtocolUtil.h" #include "barrier/ProtocolUtil.h"
#include "barrier/option_types.h" #include "barrier/option_types.h"
#include "barrier/protocol_types.h" #include "barrier/protocol_types.h"
#include "barrier/XBarrier.h"
#include "io/IStream.h" #include "io/IStream.h"
#include "base/Log.h" #include "base/Log.h"
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
@ -124,6 +125,7 @@ ServerProxy::handleData(const Event&, void*)
// parse message // parse message
LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3])); LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3]));
try {
switch ((this->*m_parser)(code)) { switch ((this->*m_parser)(code)) {
case kOkay: case kOkay:
break; break;
@ -136,6 +138,15 @@ ServerProxy::handleData(const Event&, void*)
case kDisconnect: case kDisconnect:
return; return;
} }
} catch (const XBadClient& e) {
// TODO: disconnect handling is currently dispersed across both parseMessage() and
// handleData() functions, we should collect that to a single place
LOG((CLOG_ERR "protocol error from server: %s", e.what()));
ProtocolUtil::writef(m_stream, kMsgEBad);
m_client->disconnect("invalid message from server");
return;
}
// next message // next message
n = m_stream->read(code, 4); n = m_stream->read(code, 4);

View File

@ -43,6 +43,7 @@
#define MAX_ERROR_SIZE 65535 #define MAX_ERROR_SIZE 65535
static const std::size_t MAX_INPUT_BUFFER_SIZE = 1024 * 1024;
static const float s_retryDelay = 0.01f; static const float s_retryDelay = 0.01f;
enum { enum {
@ -178,6 +179,10 @@ SecureSocket::doRead()
do { do {
m_inputBuffer.write(buffer, bytesRead); m_inputBuffer.write(buffer, bytesRead);
if (m_inputBuffer.getSize() > MAX_INPUT_BUFFER_SIZE) {
break;
}
status = secureRead(buffer, sizeof(buffer), bytesRead); status = secureRead(buffer, sizeof(buffer), bytesRead);
if (status < 0) { if (status < 0) {
return kBreak; return kBreak;

View File

@ -33,9 +33,7 @@
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
// static const std::size_t MAX_INPUT_BUFFER_SIZE = 1024 * 1024;
// TCPSocket
//
TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) : TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) :
IDataSocket(events), IDataSocket(events),
@ -345,6 +343,10 @@ TCPSocket::doRead()
do { do {
m_inputBuffer.write(buffer, (UInt32)bytesRead); m_inputBuffer.write(buffer, (UInt32)bytesRead);
if (m_inputBuffer.getSize() > MAX_INPUT_BUFFER_SIZE) {
break;
}
bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer));
} while (bytesRead > 0); } while (bytesRead > 0);

View File

@ -183,7 +183,6 @@ ClientListener::handleUnknownClient(const Event&, void* vclient)
// get the real client proxy and install it // get the real client proxy and install it
ClientProxy* client = unknownClient->orphanClientProxy(); ClientProxy* client = unknownClient->orphanClientProxy();
bool handshakeOk = true;
if (client != NULL) { if (client != NULL) {
// handshake was successful // handshake was successful
m_waitingClients.push_back(client); m_waitingClients.push_back(client);
@ -196,19 +195,11 @@ ClientListener::handleUnknownClient(const Event&, void* vclient)
&ClientListener::handleClientDisconnected, &ClientListener::handleClientDisconnected,
client)); client));
} }
else {
handshakeOk = false;
}
// now finished with unknown client // now finished with unknown client
m_events->removeHandler(m_events->forClientProxyUnknown().success(), client); m_events->removeHandler(m_events->forClientProxyUnknown().success(), client);
m_events->removeHandler(m_events->forClientProxyUnknown().failure(), client); m_events->removeHandler(m_events->forClientProxyUnknown().failure(), client);
m_newClients.erase(unknownClient); m_newClients.erase(unknownClient);
PacketStreamFilter* streamFileter = dynamic_cast<PacketStreamFilter*>(unknownClient->getStream());
IDataSocket* socket = NULL;
if (streamFileter != NULL) {
socket = dynamic_cast<IDataSocket*>(streamFileter->getStream());
}
delete unknownClient; delete unknownClient;
} }

View File

@ -51,6 +51,10 @@ ClientProxy1_0::ClientProxy1_0(const std::string& name, barrier::IStream* stream
stream->getEventTarget(), stream->getEventTarget(),
new TMethodEventJob<ClientProxy1_0>(this, new TMethodEventJob<ClientProxy1_0>(this,
&ClientProxy1_0::handleDisconnect, NULL)); &ClientProxy1_0::handleDisconnect, NULL));
m_events->adoptHandler(m_events->forIStream().inputFormatError(),
stream->getEventTarget(),
new TMethodEventJob<ClientProxy1_0>(this,
&ClientProxy1_0::handleDisconnect, NULL));
m_events->adoptHandler(m_events->forIStream().outputShutdown(), m_events->adoptHandler(m_events->forIStream().outputShutdown(),
stream->getEventTarget(), stream->getEventTarget(),
new TMethodEventJob<ClientProxy1_0>(this, new TMethodEventJob<ClientProxy1_0>(this,
@ -90,6 +94,8 @@ ClientProxy1_0::removeHandlers()
getStream()->getEventTarget()); getStream()->getEventTarget());
m_events->removeHandler(m_events->forIStream().outputShutdown(), m_events->removeHandler(m_events->forIStream().outputShutdown(),
getStream()->getEventTarget()); getStream()->getEventTarget());
m_events->removeHandler(m_events->forIStream().inputFormatError(),
getStream()->getEventTarget());
m_events->removeHandler(Event::kTimer, this); m_events->removeHandler(Event::kTimer, this);
// remove timer // remove timer
@ -148,12 +154,21 @@ ClientProxy1_0::handleData(const Event&, void*)
} }
// parse message // parse message
try {
LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
if (!(this->*m_parser)(code)) { if (!(this->*m_parser)(code)) {
LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3]));
disconnect(); disconnect();
return; return;
} }
} catch (const XBadClient& e) {
// TODO: disconnect handling is currently dispersed across both parseMessage() and
// handleData() functions, we should collect that to a single place
LOG((CLOG_ERR "protocol error from client: %s", e.what()));
disconnect();
return;
}
// next message // next message
n = getStream()->read(code, 4); n = getStream()->read(code, 4);
@ -173,6 +188,8 @@ ClientProxy1_0::parseHandshakeMessage(const UInt8* code)
} }
else if (memcmp(code, kMsgDInfo, 4) == 0) { else if (memcmp(code, kMsgDInfo, 4) == 0) {
// future messages get parsed by parseMessage // future messages get parsed by parseMessage
// NOTE: we're taking address of virtual function here,
// not ClientProxy1_0 implementation of it.
m_parser = &ClientProxy1_0::parseMessage; m_parser = &ClientProxy1_0::parseMessage;
if (recvInfo()) { if (recvInfo()) {
m_events->addEvent(Event(m_events->forClientProxy().ready(), getEventTarget())); m_events->addEvent(Event(m_events->forClientProxy().ready(), getEventTarget()));

View File

@ -118,6 +118,10 @@ ClientProxyUnknown::addStreamHandlers()
m_stream->getEventTarget(), m_stream->getEventTarget(),
new TMethodEventJob<ClientProxyUnknown>(this, new TMethodEventJob<ClientProxyUnknown>(this,
&ClientProxyUnknown::handleDisconnect)); &ClientProxyUnknown::handleDisconnect));
m_events->adoptHandler(m_events->forIStream().inputFormatError(),
m_stream->getEventTarget(),
new TMethodEventJob<ClientProxyUnknown>(this,
&ClientProxyUnknown::handleDisconnect));
m_events->adoptHandler(m_events->forIStream().outputShutdown(), m_events->adoptHandler(m_events->forIStream().outputShutdown(),
m_stream->getEventTarget(), m_stream->getEventTarget(),
new TMethodEventJob<ClientProxyUnknown>(this, new TMethodEventJob<ClientProxyUnknown>(this,
@ -149,6 +153,8 @@ ClientProxyUnknown::removeHandlers()
m_stream->getEventTarget()); m_stream->getEventTarget());
m_events->removeHandler(m_events->forIStream().inputShutdown(), m_events->removeHandler(m_events->forIStream().inputShutdown(),
m_stream->getEventTarget()); m_stream->getEventTarget());
m_events->removeHandler(m_events->forIStream().inputFormatError(),
m_stream->getEventTarget());
m_events->removeHandler(m_events->forIStream().outputShutdown(), m_events->removeHandler(m_events->forIStream().outputShutdown(),
m_stream->getEventTarget()); m_stream->getEventTarget());
} }