Skip to content

Commit 10f2446

Browse files
committed
Message sizes: fix integer overflow in send path, clarify limits
Add k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal to accommodate the P2PMessageHeader prepended by ISteamNetworkingMessages, so callers can use the full advertised message size. Add GetEffectiveRecvMaxMessageSize() on connections owned by a messages endpoint to match. Move size checks to API entry points (SpewWarning + k_EResultInvalidParam) and demote the innermost check to an Assert against the internal limit. This addresses issue #382. I don't believe there is a real security vuln here in the threat model where the local process is trusted. A malicious remote peer cannot trigger this overflow.
1 parent 7b27b4a commit 10f2446

4 files changed

Lines changed: 81 additions & 38 deletions

File tree

src/steamnetworkingsockets/clientlib/csteamnetworkingmessages.cpp

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,14 @@
33
#include "csteamnetworkingmessages.h"
44
#include "csteamnetworkingsockets.h"
55
#include "steamnetworkingsockets_p2p.h"
6+
#include "../steamnetworkingsockets_internal.h"
67

78
// memdbgon must be the last include file in a .cpp file!!!
89
#include "tier0/memdbgon.h"
910

1011
// Make sure we're enabled
1112
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_STEAMNETWORKINGMESSAGES
1213

13-
#pragma pack(push,1)
14-
struct P2PMessageHeader
15-
{
16-
uint8 m_nFlags;
17-
int m_nToChannel;
18-
};
19-
#pragma pack(pop)
20-
COMPILE_TIME_ASSERT( sizeof(P2PMessageHeader) == 5 );
21-
2214
// FIXME TODO:
2315
// * Need to clear P2P error when we start connecting or get a successful result
2416
// * When we get P2P error callback from steam, need to flow that back up through to the session
@@ -29,6 +21,18 @@ COMPILE_TIME_ASSERT( sizeof(P2PMessageHeader) == 5 );
2921
// Put everything in a namespace, so we don't violate the one definition rule
3022
namespace SteamNetworkingSocketsLib {
3123

24+
// Messages sent using the ISteamnNetworkingMessages interface are prepended
25+
// internally by a small internal header
26+
#pragma pack(push,1)
27+
struct P2PMessageHeader
28+
{
29+
uint8 m_nFlags;
30+
int m_nToChannel;
31+
};
32+
#pragma pack(pop)
33+
COMPILE_TIME_ASSERT( sizeof(P2PMessageHeader) == 5 );
34+
COMPILE_TIME_ASSERT( k_cbMaxSteamNetworkingSocketsMessageSizeSend + sizeof(P2PMessageHeader) <= k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal );
35+
3236
const SteamNetworkingMicroseconds k_usecSteamNetworkingP2PSessionIdleTimeout = 3*60*k_nMillion;
3337
const int k_ESteamNetConnectionEnd_P2P_SessionClosed = k_ESteamNetConnectionEnd_App_Min + 1;
3438
const int k_ESteamNetConnectionEnd_P2P_SessionIdleTimeout = k_ESteamNetConnectionEnd_App_Min + 2;
@@ -347,7 +351,7 @@ void CMessagesEndPointSession::ClearActiveConnection()
347351
CSteamNetworkingMessages::Channel::Channel()
348352
{
349353
m_queueRecvMessages.m_pRequiredLock = &g_lockAllRecvMessageQueues;
350-
354+
351355
}
352356

353357
CSteamNetworkingMessages::Channel::~Channel()
@@ -418,8 +422,15 @@ EResult CSteamNetworkingMessages::SendMessageToUser( const SteamNetworkingIdenti
418422
{
419423
if ( identityRemote.IsInvalid() )
420424
{
421-
AssertMsg( false, "Identity isn't valid for Messages sessions." );
422-
return k_EResultFail;
425+
SpewWarning( "Identity %s isn't valid for Messages sessions.", SteamNetworkingIdentityRender( identityRemote ).c_str() );
426+
return k_EResultInvalidParam;
427+
}
428+
429+
// Message too big?
430+
if ( cubData > (unsigned)k_cbMaxSteamNetworkingSocketsMessageSizeSend )
431+
{
432+
SpewWarning( "Message size %u is too big. Max is %d", cubData, k_cbMaxSteamNetworkingSocketsMessageSizeSend );
433+
return k_EResultInvalidParam;
423434
}
424435

425436
SteamNetworkingGlobalLock scopeLock( "SendMessageToUser" ); // !SPEED! Can we avoid this?
@@ -486,7 +497,7 @@ EResult CSteamNetworkingMessages::SendMessageToUser( const SteamNetworkingIdenti
486497
nSendFlags = k_nSteamNetworkingSend_Reliable;
487498

488499
// Allocate a message, and put our header in front.
489-
int cbSend = cubData + sizeof(P2PMessageHeader);
500+
int cbSend = (int)cubData + (int)sizeof(P2PMessageHeader);
490501
CSteamNetworkingMessage *pMsg = (CSteamNetworkingMessage *)m_steamNetworkingSockets.m_pSteamNetworkingUtils->AllocateMessage( cbSend );
491502
if ( !pMsg )
492503
{

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ CSteamNetworkConnectionBase::CSteamNetworkConnectionBase( CSteamNetworkingSocket
579579
m_bConnectionInitiatedRemotely = false;
580580
m_pTransport = nullptr;
581581
m_nSupressStateChangeCallbacks = 0;
582-
582+
583583
// Initialize configuration using parent interface for now.
584584
m_connectionConfig.Init( &m_pSteamNetworkingSocketsInterface->m_connectionConfig );
585585

@@ -1400,7 +1400,7 @@ ESteamNetConnectionEnd CSteamNetworkConnectionBase::RecvCryptoHandshake(
14001400
m_sCryptRemote = msgSessionInfo.info();
14011401

14021402
// If they presented a signature, it must be valid
1403-
const CertAuthScope *pCACertAuthScope = nullptr;
1403+
const CertAuthScope *pCACertAuthScope = nullptr;
14041404
if ( msgCert.has_ca_signature() )
14051405
{
14061406

@@ -1599,7 +1599,7 @@ ESteamNetConnectionEnd CSteamNetworkConnectionBase::FinishCryptoHandshake( bool
15991599
SetCryptoCipherList();
16001600
}
16011601
Assert( m_msgCryptLocal.ciphers_size() > 0 );
1602-
1602+
16031603
// Find a mutually-acceptable cipher
16041604
Assert( m_eNegotiatedCipher == k_ESteamNetworkingSocketsCipher_INVALID );
16051605
m_eNegotiatedCipher = k_ESteamNetworkingSocketsCipher_INVALID;
@@ -2027,6 +2027,13 @@ EResult CSteamNetworkConnectionBase::APISendMessageToConnection( const void *pDa
20272027
return k_EResultNoConnection;
20282028
}
20292029

2030+
// Check message size
2031+
if ( cbData > (unsigned)k_cbMaxSteamNetworkingSocketsMessageSizeSend )
2032+
{
2033+
SpewWarning( "Message size %u is too big. Max is %d", cbData, k_cbMaxSteamNetworkingSocketsMessageSizeSend );
2034+
return k_EResultInvalidParam;
2035+
}
2036+
20302037
// Fill out a message object
20312038
CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( cbData );
20322039
if ( !pMsg )
@@ -2083,16 +2090,27 @@ int64 CSteamNetworkConnectionBase::APISendMessageToConnection( CSteamNetworkingM
20832090
return -k_EResultNoConnection;
20842091
}
20852092

2093+
// Message too big?
2094+
if ( (unsigned)pMsg->m_cbSize > (unsigned)k_cbMaxSteamNetworkingSocketsMessageSizeSend )
2095+
{
2096+
SpewWarning( "Message size %u is too big. Max is %d", pMsg->m_cbSize, k_cbMaxSteamNetworkingSocketsMessageSizeSend );
2097+
pMsg->Release();
2098+
return -k_EResultInvalidParam;
2099+
}
2100+
20862101
return _APISendMessageToConnection( pMsg, usecNow, pbThinkImmediately );
20872102
}
20882103

20892104
int64 CSteamNetworkConnectionBase::_APISendMessageToConnection( CSteamNetworkingMessage *pMsg, SteamNetworkingMicroseconds usecNow, bool *pbThinkImmediately )
20902105
{
20912106

20922107
// Message too big?
2093-
if ( pMsg->m_cbSize > k_cbMaxSteamNetworkingSocketsMessageSizeSend )
2108+
// NOTE - we should have detected this higher up in the call stack.
2109+
// Also, use unsigned math here just in case we have a bug and the size is
2110+
// actually negative somehow
2111+
if ( (unsigned)pMsg->m_cbSize > (unsigned)k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal )
20942112
{
2095-
AssertMsg2( false, "Message size %d is too big. Max is %d", pMsg->m_cbSize, k_cbMaxSteamNetworkingSocketsMessageSizeSend );
2113+
AssertMsg2( false, "Message size %d is too big. Max internal size is %d", pMsg->m_cbSize, k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal );
20962114
pMsg->Release();
20972115
return -k_EResultInvalidParam;
20982116
}
@@ -2137,7 +2155,7 @@ int CSteamNetworkConnectionBase::APIReceiveMessages( SteamNetworkingMessage_t **
21372155
m_pLock->AssertHeldByCurrentThread();
21382156

21392157
g_lockAllRecvMessageQueues.lock();
2140-
2158+
21412159
int result = m_queueRecvMessages.RemoveMessages( ppOutMessages, nMaxMessages );
21422160
g_lockAllRecvMessageQueues.unlock();
21432161

@@ -2204,7 +2222,7 @@ bool CSteamNetworkConnectionBase::DecryptDataChunk( uint16 nWireSeqNum, int cbPa
22042222

22052223
// Restore the IV to the base value
22062224
*(uint64 *)&m_cryptIVRecv.m_buf -= LittleQWord( ctx.m_nPktNum );
2207-
2225+
22082226
// Did decryption fail?
22092227
if ( !bDecryptOK ) {
22102228

@@ -2646,10 +2664,11 @@ CSteamNetworkingMessage *CSteamNetworkConnectionBase::AllocateNewRecvMessage( ui
26462664
//
26472665

26482666
// Max message size
2649-
if ( (uint32)m_connectionConfig.RecvMaxMessageSize.Get() < cbSize )
2667+
const int cbRecvMaxMessageSize = GetEffectiveRecvMaxMessageSize();
2668+
if ( (unsigned)cbRecvMaxMessageSize < cbSize )
26502669
{
2651-
SpewMsg( "[%s] recv message of size %u too large for limit of %d.\n", GetDescription(), cbSize, m_connectionConfig.RecvMaxMessageSize.Get() );
2652-
ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Failed to allocate a buffer of size %u (limit is %d).", cbSize, m_connectionConfig.RecvMaxMessageSize.Get() );
2670+
SpewMsg( "[%s] recv message of size %u too large for limit of %d.\n", GetDescription(), cbSize, cbRecvMaxMessageSize );
2671+
ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Failed to allocate a buffer of size %u (limit is %d).", cbSize, cbRecvMaxMessageSize );
26532672
return nullptr;
26542673
}
26552674

@@ -3702,7 +3721,7 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::ThinkConnection_ClientC
37023721
Assert( !m_bConnectionInitiatedRemotely );
37033722

37043723
// Default behaviour for client periodically sending connect requests
3705-
3724+
37063725
// Ask transport if it's ready
37073726
if ( !m_pTransport || !m_pTransport->BCanSendEndToEndConnectRequest() )
37083727
return usecNow + k_nMillion/20; // Nope, check back in just a bit.

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct RemoteConnectionKey_t
9191
}
9292
};
9393

94-
/// Base class for connection-type-specific context structure
94+
/// Base class for connection-type-specific context structure
9595
struct SendPacketContext_t
9696
{
9797
inline SendPacketContext_t( SteamNetworkingMicroseconds usecNow, const char *pszReason ) : m_usecNow( usecNow ), m_pszReason( pszReason ) {}
@@ -353,7 +353,7 @@ struct ConnectionScopeLock : ScopeLock<ConnectionLock>
353353
/// Abstract interface for a connection to a remote host over any underlying
354354
/// transport. Most of the common functionality for implementing reliable
355355
/// connections on top of unreliable datagrams, connection quality measurement,
356-
/// etc is implemented here.
356+
/// etc is implemented here.
357357
class CSteamNetworkConnectionBase : public ILockableThinker< ConnectionLock >
358358
{
359359
public:
@@ -678,6 +678,18 @@ class CSteamNetworkConnectionBase : public ILockableThinker< ConnectionLock >
678678
static constexpr CMessagesEndPointSession *m_pMessagesEndPointSessionOwner = nullptr;
679679
#endif
680680

681+
/// Get the effective value of the RecvMaxMessageSize value.
682+
/// Connections used by the ISteamNetworkingMessages interface need a little extra space
683+
int GetEffectiveRecvMaxMessageSize() const
684+
{
685+
int cbResult = m_connectionConfig.RecvMaxMessageSize.Get();
686+
if ( m_pMessagesEndPointSessionOwner )
687+
{
688+
cbResult += k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal - k_cbMaxSteamNetworkingSocketsMessageSizeSend;
689+
}
690+
return cbResult;
691+
}
692+
681693
/// Time when we would like to send our next connection diagnostics
682694
/// update. This is initialized the first time we enter the "connecting"
683695
/// state and we are on a platform that wants those updates.
@@ -799,7 +811,7 @@ class CSteamNetworkConnectionBase : public ILockableThinker< ConnectionLock >
799811
// combined with the packet number so that the effective IV is
800812
// unique per packet. We use a 96-bit IV, which is what TLS uses
801813
// (RFC5288), what NIST recommends (https://dl.acm.org/citation.cfm?id=2206251),
802-
// and what makes GCM the most efficient.
814+
// and what makes GCM the most efficient.
803815
AutoWipeFixedSizeBuffer<12> m_cryptIVSend;
804816
AutoWipeFixedSizeBuffer<12> m_cryptIVRecv;
805817

@@ -972,11 +984,11 @@ class CConnectionTransport
972984
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) = 0;
973985

974986
/// Connection will call this to ask the transport to surround the
975-
/// "chunk" with the appropriate framing, and route it to the
976-
/// appropriate host. A "chunk" might contain a mix of reliable
977-
/// and unreliable data. We use the same framing for data
978-
/// payloads for all connection types. Return value is
979-
/// the number of bytes written to the network layer, UDP/IP
987+
/// "chunk" with the appropriate framing, and route it to the
988+
/// appropriate host. A "chunk" might contain a mix of reliable
989+
/// and unreliable data. We use the same framing for data
990+
/// payloads for all connection types. Return value is
991+
/// the number of bytes written to the network layer, UDP/IP
980992
/// header is not included.
981993
///
982994
/// ctx is whatever the transport passed to SNP_SendPacket, if the
@@ -1116,7 +1128,7 @@ extern CUtlHashMap<int, CSteamNetworkPollGroup *, std::equal_to<int>, Identity<i
11161128
// All of the tables above are projected by the same lock, since we expect to only access it briefly
11171129
struct TableLock : Lock<RecursiveTimedMutexImpl> {
11181130
TableLock() : Lock<RecursiveTimedMutexImpl>( "table", LockDebugInfo::k_nFlag_Table, LockDebugInfo::k_nOrder_ObjectOrTable ) {}
1119-
};
1131+
};
11201132
using TableScopeLock = ScopeLock<TableLock>;
11211133
extern TableLock g_tables_lock;
11221134

src/steamnetworkingsockets/steamnetworkingsockets_internal.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,6 @@ const int k_cbSteamNetworkingSocketsTypicalMaxPlaintextPayloadSend = k_cbSteamNe
287287
const int k_cbSteamNetworkingSocketsMaxEncryptedPayloadRecv = k_cbSteamNetworkingSocketsMaxUDPMsgLen;
288288
const int k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv = k_cbSteamNetworkingSocketsMaxUDPMsgLen;
289289

290-
/// Max value that RecvMaxMessageSize can be set to.
291-
const int k_cbMaxMessageSizeRecv_Limit = k_cbMaxSteamNetworkingSocketsMessageSizeSend*2;
292-
COMPILE_TIME_ASSERT( k_cbMaxMessageSizeRecv_Limit >= k_cbMaxSteamNetworkingSocketsMessageSizeSend*2 );
293-
294290
/// If we have a cert that is going to expire in <N seconds, try to renew it
295291
const int k_nSecCertExpirySeekRenew = 3600*2;
296292

@@ -303,6 +299,11 @@ COMPILE_TIME_ASSERT( k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend + 50 < k_
303299
/// Min size of raw UDP message.
304300
const int k_nMinSteamDatagramUDPMsgLen = 5;
305301

302+
/// Max message size we will actually send, internally. This is a bit of a kludge because the ISteamNetworkingMessages
303+
/// API prepends messages with a small header, and we want users of that API to be able to use the full message size,
304+
/// without having to know about this internal header.
305+
const int k_cbMaxSteamNetworkingSocketsMessageSizeSend_Internal = k_cbMaxSteamNetworkingSocketsMessageSizeSend + 16;
306+
306307
/// When sending a stats message, what sort of reply is requested by the calling code?
307308
enum EStatsReplyRequest
308309
{
@@ -870,7 +871,7 @@ struct ConnectionConfig
870871
ConfigValue<int32> SendBufferSize;
871872
ConfigValue<int32> RecvBufferSize;
872873
ConfigValue<int32> RecvBufferMessages;
873-
ConfigValue<int32> RecvMaxMessageSize;
874+
ConfigValue<int32> RecvMaxMessageSize; // NOTE - use GetEffectiveRecvMaxMessageSize()!
874875
ConfigValue<int32> RecvMaxSegmentsPerPacket;
875876
ConfigValue<int32> SendRateMin;
876877
ConfigValue<int32> SendRateMax;

0 commit comments

Comments
 (0)