r5sdk/r5dev/engine/client/client.cpp
Kawe Mazidjatari 89431cc61f Fix crasher by clamping stringcmd length before tokenizing it
Possible crasher is to send a stringcmd >= 512 in size with funny UTF8 characters and have CUtlBuffer::ParseToken() read past it. Apparently seems to be mostly a problem on 32bit? I was unable to initiate a crash, though one string caused interesting behavior before, and there was one report of the dedicated server being 'crashed' with this. There is no reason to tokenize it up to 512 bytes if the game is only ever going to allow 128, so clamp it to 129 and if the user exceeds it then they still get the message and we just jettison it.
2023-08-07 16:52:35 +02:00

263 lines
9.8 KiB
C++

//===============================================================================//
//
// Purpose:
//
// $NoKeywords: $
//
//===============================================================================//
// client.cpp: implementation of the CClient class.
//
///////////////////////////////////////////////////////////////////////////////////
#include "core/stdafx.h"
#include "tier1/cvar.h"
#include "tier1/strtools.h"
#include "engine/server/server.h"
#include "engine/client/client.h"
// 128+1 so that the client still receives the 'console command too long' message.
#define STRINGCMD_MAX_LEN 129
//---------------------------------------------------------------------------------
// Purpose: throw away any residual garbage in the channel
//---------------------------------------------------------------------------------
void CClient::Clear(void)
{
#ifndef CLIENT_DLL
g_ServerPlayer[GetUserID()].Reset(); // Reset ServerPlayer slot.
#endif // !CLIENT_DLL
v_CClient_Clear(this);
}
//---------------------------------------------------------------------------------
// Purpose: throw away any residual garbage in the channel
// Input : *pClient -
//---------------------------------------------------------------------------------
void CClient::VClear(CClient* pClient)
{
pClient->Clear();
}
//---------------------------------------------------------------------------------
// Purpose: connect new client
// Input : *szName -
// *pNetChannel -
// bFakePlayer -
// *a5 -
// *szMessage -
// nMessageSize -
// Output : true if connection was successful, false otherwise
//---------------------------------------------------------------------------------
bool CClient::Connect(const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize)
{
return v_CClient_Connect(this, szName, pNetChannel, bFakePlayer, a5, szMessage, nMessageSize);
}
//---------------------------------------------------------------------------------
// Purpose: connect new client
// Input : *pClient -
// *szName -
// *pNetChannel -
// bFakePlayer -
// *a5 -
// *szMessage -
// nMessageSize -
// Output : true if connection was successful, false otherwise
//---------------------------------------------------------------------------------
bool CClient::VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize)
{
bool bResult = v_CClient_Connect(pClient, szName, pNetChannel, bFakePlayer, a5, szMessage, nMessageSize);
#ifndef CLIENT_DLL
g_ServerPlayer[pClient->GetUserID()].Reset(); // Reset ServerPlayer slot.
#endif // !CLIENT_DLL
return bResult;
}
//---------------------------------------------------------------------------------
// Purpose: disconnect client
// Input : nRepLvl -
// *szReason -
// ... -
//---------------------------------------------------------------------------------
void CClient::Disconnect(const Reputation_t nRepLvl, const char* szReason, ...)
{
if (m_nSignonState != SIGNONSTATE::SIGNONSTATE_NONE)
{
char szBuf[1024];
{/////////////////////////////
va_list vArgs;
va_start(vArgs, szReason);
vsnprintf(szBuf, sizeof(szBuf), szReason, vArgs);
szBuf[sizeof(szBuf) - 1] = '\0';
va_end(vArgs);
}/////////////////////////////
v_CClient_Disconnect(this, nRepLvl, szBuf);
}
}
//---------------------------------------------------------------------------------
// Purpose: activate player
// Input : *pClient -
//---------------------------------------------------------------------------------
void CClient::VActivatePlayer(CClient* pClient)
{
// Set the client instance to 'ready' before calling ActivatePlayer.
pClient->SetPersistenceState(PERSISTENCE::PERSISTENCE_READY);
v_CClient_ActivatePlayer(pClient);
#ifndef CLIENT_DLL
const CNetChan* pNetChan = pClient->GetNetChan();
if (pNetChan && sv_showconnecting->GetBool())
{
DevMsg(eDLL_T::SERVER, "Activated player #%d; channel %s(%s) ('%llu')\n",
pClient->GetUserID(), pNetChan->GetName(), pNetChan->GetAddress(), pClient->GetNucleusID());
}
#endif // !CLIENT_DLL
}
//---------------------------------------------------------------------------------
// Purpose: send a net message with replay.
// set 'CNetMessage::m_nGroup' to 'NoReplay' to disable replay.
// Input : *pMsg -
// bLocal -
// bForceReliable -
// bVoice -
//---------------------------------------------------------------------------------
bool CClient::SendNetMsgEx(CNetMessage* pMsg, char bLocal, bool bForceReliable, bool bVoice)
{
if (!ShouldReplayMessage(pMsg))
{
// Don't copy the message into the replay buffer.
pMsg->m_nGroup = NetMessageGroup::NoReplay;
}
return v_CClient_SendNetMsgEx(this, pMsg, bLocal, bForceReliable, bVoice);
}
//---------------------------------------------------------------------------------
// Purpose: send a snapshot
// Input : *pClient -
// *pFrame -
// nTick -
// nTickAck -
//---------------------------------------------------------------------------------
void* CClient::VSendSnapshot(CClient* pClient, CClientFrame* pFrame, int nTick, int nTickAck)
{
return v_CClient_SendSnapshot(pClient, pFrame, nTick, nTickAck);
}
//---------------------------------------------------------------------------------
// Purpose: process string commands (kicking anyone attempting to DOS)
// Input : *pClient - (ADJ)
// *pMsg -
// Output : false if cmd should be passed to CServerGameClients
//---------------------------------------------------------------------------------
bool CClient::VProcessStringCmd(CClient* pClient, NET_StringCmd* pMsg)
{
#ifndef CLIENT_DLL
#if defined (GAMEDLL_S0) || defined (GAMEDLL_S1)
CClient* pClient_Adj = pClient;
#elif defined (GAMEDLL_S2) || defined (GAMEDLL_S3)
/* Original function called method "CClient::ExecuteStringCommand" with an optimization
* that shifted the 'this' pointer with 8 bytes.
* Since this has been inlined with "CClient::ProcessStringCmd" as of S2, the shifting
* happens directly to anything calling this function. */
char* pShifted = reinterpret_cast<char*>(pClient) - 8;
CClient* pClient_Adj = reinterpret_cast<CClient*>(pShifted);
#endif // !GAMEDLL_S0 || !GAMEDLL_S1
int nUserID = pClient_Adj->GetUserID();
ServerPlayer_t* pSlot = &g_ServerPlayer[nUserID];
double flStartTime = Plat_FloatTime();
int nCmdQuotaLimit = sv_quota_stringCmdsPerSecond->GetInt();
if (!nCmdQuotaLimit)
return true;
const char* pCmd = pMsg->cmd;
// Just skip if the cmd pointer is null, we still check if the
// client sent too many commands and take appropriate actions.
// The internal function discards the command if it's null.
if (pCmd)
{
// If the string length exceeds 128, the will engine return a 'command
// string too long' message back to the client that issued it and
// subsequently jettison the string cmd. Before this routine gets hit,
// the entire string gets parsed (up to 512 bytes). There is an issue
// in CUtlBuffer::ParseToken() that causes it to read past its buffer;
// mostly seems to happen on 32bit, but a carefully crafted string
// should work on 64bit too). The fix is to just null everything past
// the maximum allowed length. The second 'theoretical' fix would be to
// properly fix CUtlBuffer::ParseToken() by computing the UTF8 character
// size each iteration and check if it still doesn't exceed bounds.
memset(&pMsg->buffer[STRINGCMD_MAX_LEN],
'\0', sizeof(pMsg->buffer) - (STRINGCMD_MAX_LEN));
if (!V_IsValidUTF8(pCmd))
{
Warning(eDLL_T::SERVER, "Removing client '%s' from slot #%i ('%llu' sent invalid string command!)\n",
pClient_Adj->GetNetChan()->GetAddress(), pClient_Adj->GetUserID(), pClient_Adj->GetNucleusID());
pClient_Adj->Disconnect(Reputation_t::REP_MARK_BAD, "#DISCONNECT_INVALID_STRINGCMD");
return true;
}
}
if (flStartTime - pSlot->m_flStringCommandQuotaTimeStart >= 1.0)
{
pSlot->m_flStringCommandQuotaTimeStart = flStartTime;
pSlot->m_nStringCommandQuotaCount = 0;
}
++pSlot->m_nStringCommandQuotaCount;
if (pSlot->m_nStringCommandQuotaCount > nCmdQuotaLimit)
{
Warning(eDLL_T::SERVER, "Removing client '%s' from slot #%i ('%llu' exceeded string command quota!)\n",
pClient_Adj->GetNetChan()->GetAddress(), pClient_Adj->GetUserID(), pClient_Adj->GetNucleusID());
pClient_Adj->Disconnect(Reputation_t::REP_MARK_BAD, "#DISCONNECT_STRINGCMD_OVERFLOW");
return true;
}
#endif // !CLIENT_DLL
return v_CClient_ProcessStringCmd(pClient, pMsg);
}
//---------------------------------------------------------------------------------
// Purpose: internal hook to 'CClient::SendNetMsgEx'
// Input : *pClient -
// *pMsg -
// bLocal -
// bForceReliable -
// bVoice -
//---------------------------------------------------------------------------------
bool CClient::VSendNetMsgEx(CClient* pClient, CNetMessage* pMsg, char bLocal, bool bForceReliable, bool bVoice)
{
return pClient->SendNetMsgEx(pMsg, bLocal, bForceReliable, bVoice);
}
void VClient::Attach(void) const
{
#ifndef CLIENT_DLL
DetourAttach((LPVOID*)&v_CClient_Clear, &CClient::VClear);
DetourAttach((LPVOID*)&v_CClient_Connect, &CClient::VConnect);
DetourAttach((LPVOID*)&v_CClient_ActivatePlayer, &CClient::VActivatePlayer);
DetourAttach((LPVOID*)&v_CClient_ProcessStringCmd, &CClient::VProcessStringCmd);
DetourAttach((LPVOID*)&v_CClient_SendNetMsgEx, &CClient::VSendNetMsgEx);
//DetourAttach((LPVOID*)&p_CClient_SendSnapshot, &CClient::VSendSnapshot);
#endif // !CLIENT_DLL
}
void VClient::Detach(void) const
{
#ifndef CLIENT_DLL
DetourDetach((LPVOID*)&v_CClient_Clear, &CClient::VClear);
DetourDetach((LPVOID*)&v_CClient_Connect, &CClient::VConnect);
DetourDetach((LPVOID*)&v_CClient_ActivatePlayer, &CClient::VActivatePlayer);
DetourDetach((LPVOID*)&v_CClient_ProcessStringCmd, &CClient::VProcessStringCmd);
DetourDetach((LPVOID*)&v_CClient_SendNetMsgEx, &CClient::VSendNetMsgEx);
//DetourDetach((LPVOID*)&p_CClient_SendSnapshot, &CClient::VSendSnapshot);
#endif // !CLIENT_DLL
}