mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
CClient: add rate limit logic for 'ProcessStringCmd'
Client's can run string commands on the server with no rate limit. This means when you run 50k+ commands that are unknown, or spam 30k 'status' commands, you will be able to hang the server for 800/1200ms (15k/30kms if script printing to console is enabled!). Although the netchan processing budget system will kick you, the damage has already been done at this point. This change effectively breaks the ability to DOS the server from the client using networked string commands. In easier words; binding 'status' to your mousewheel will get you kicked from the server, without hitching the server.
This commit is contained in:
parent
793c2e8e50
commit
7804241376
@ -87,6 +87,13 @@ public:
|
||||
bf_write m_DataOut;
|
||||
};
|
||||
|
||||
struct NET_StringCmd : CNetMessage, INetMessageHandler
|
||||
{
|
||||
const char* cmd;
|
||||
char buffer[1024];
|
||||
};
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// MM_HEARTBEAT
|
||||
//-------------------------------------------------------------------------
|
||||
|
@ -9,6 +9,7 @@
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
#include "core/stdafx.h"
|
||||
#include "tier1/cvar.h"
|
||||
#include "engine/server/server.h"
|
||||
#include "engine/client/client.h"
|
||||
|
||||
@ -279,16 +280,64 @@ void CClient::Disconnect(const Reputation_t nRepLvl, const char* szReason, ...)
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
// 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
|
||||
ServerPlayer_t* pSlot = &g_ServerPlayer[pClient_Adj->GetUserID()];
|
||||
double flStartTime = Plat_FloatTime();
|
||||
int nCmdQuota = sv_quota_stringCmdsPerSecond->GetInt();
|
||||
|
||||
if (!nCmdQuota)
|
||||
return true;
|
||||
|
||||
if (flStartTime - pSlot->m_flStringCommandQuotaTimeStart >= 1.0)
|
||||
{
|
||||
pSlot->m_flStringCommandQuotaTimeStart = flStartTime;
|
||||
pSlot->m_nStringCommandQuotaCount = 0;
|
||||
}
|
||||
++pSlot->m_nStringCommandQuotaCount;
|
||||
|
||||
if (pSlot->m_nStringCommandQuotaCount > nCmdQuota)
|
||||
{
|
||||
Warning(eDLL_T::SERVER, "Removing client '%s' from slot '%i' ('%llu' exceeded string command quota!)\n",
|
||||
pClient_Adj->GetNetChan()->GetAddress(), pClient_Adj->GetUserID(), pClient->GetNucleusID());
|
||||
|
||||
pClient_Adj->Disconnect(Reputation_t::REP_MARK_BAD, "#DISCONNECT_STRINGCMD_OVERFLOW");
|
||||
return true;
|
||||
}
|
||||
#endif // !CLIENT_DLL
|
||||
|
||||
return v_CClient_ProcessStringCmd(pClient, pMsg);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
void CBaseClient_Attach()
|
||||
{
|
||||
DetourAttach((LPVOID*)&v_CClient_Clear, &CClient::VClear);
|
||||
DetourAttach((LPVOID*)&v_CClient_Connect, &CClient::VConnect);
|
||||
DetourAttach((LPVOID*)&v_CClient_ProcessStringCmd, &CClient::VProcessStringCmd);
|
||||
}
|
||||
void CBaseClient_Detach()
|
||||
{
|
||||
DetourDetach((LPVOID*)&v_CClient_Clear, &CClient::VClear);
|
||||
DetourDetach((LPVOID*)&v_CClient_Connect, &CClient::VConnect);
|
||||
DetourDetach((LPVOID*)&v_CClient_ProcessStringCmd, &CClient::VProcessStringCmd);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -22,7 +22,7 @@ class CClient;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
extern CClient* g_pClient;
|
||||
|
||||
class CClient : INetChannelHandler, IClientMessageHandler
|
||||
class CClient : IClientMessageHandler, INetChannelHandler
|
||||
{
|
||||
public:
|
||||
CClient* GetClient(int nIndex) const;
|
||||
@ -52,6 +52,7 @@ public:
|
||||
static bool VConnect(CClient* pClient, const char* szName, void* pNetChannel, bool bFakePlayer, void* a5, char* szMessage, int nMessageSize);
|
||||
void Clear(void);
|
||||
static void VClear(CClient* pClient);
|
||||
static bool VProcessStringCmd(CClient* pClient, NET_StringCmd* pMsg);
|
||||
|
||||
private:
|
||||
uint32_t m_nUserID; //0x0010
|
||||
@ -106,6 +107,9 @@ inline auto v_CClient_Disconnect = p_CClient_Disconnect.RCast<bool (*)(CClient*
|
||||
inline CMemory p_CClient_Clear;
|
||||
inline auto v_CClient_Clear = p_CClient_Clear.RCast<void (*)(CClient* pClient)>();
|
||||
|
||||
inline CMemory p_CClient_ProcessStringCmd;
|
||||
inline auto v_CClient_ProcessStringCmd = p_CClient_ProcessStringCmd.RCast<bool (*)(CClient* pClient, NET_StringCmd* pMsg)>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void CBaseClient_Attach();
|
||||
void CBaseClient_Detach();
|
||||
@ -118,6 +122,7 @@ class VClient : public IDetour
|
||||
spdlog::debug("| FUN: CClient::Connect : {:#18x} |\n", p_CClient_Connect.GetPtr());
|
||||
spdlog::debug("| FUN: CClient::Disconnect : {:#18x} |\n", p_CClient_Disconnect.GetPtr());
|
||||
spdlog::debug("| FUN: CClient::Clear : {:#18x} |\n", p_CClient_Clear.GetPtr());
|
||||
spdlog::debug("| FUN: CClient::ProcessStringCmd : {:#18x} |\n", p_CClient_ProcessStringCmd.GetPtr());
|
||||
spdlog::debug("| VAR: g_pClient[128] : {:#18x} |\n", reinterpret_cast<uintptr_t>(g_pClient));
|
||||
spdlog::debug("+----------------------------------------------------------------+\n");
|
||||
}
|
||||
@ -130,10 +135,16 @@ class VClient : public IDetour
|
||||
p_CClient_Disconnect = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x48\x8B\xC4\x4C\x89\x40\x18\x4C\x89\x48\x20\x53\x56\x57\x48\x81\xEC\x00\x00\x00\x00\x83\xB9\x00\x00\x00\x00\x00\x49\x8B\xF8\x8B\xF2"), "xxxxxxxxxxxxxxxxx????xx?????xxxxx");
|
||||
#endif
|
||||
p_CClient_Clear = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x40\x53\x41\x56\x41\x57\x48\x83\xEC\x20\x48\x8B\xD9\x48\x89\x74"), "xxxxxxxxxxxxxxxx");
|
||||
#if defined (GAMEDLL_S0) || defined (GAMEDLL_S1)
|
||||
p_CClient_ProcessStringCmd = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x48\x83\xEC\x28\x4C\x8B\x42\x20"), "xxxxxxxx");
|
||||
#elif defined (GAMEDLL_S2) || defined (GAMEDLL_S3)
|
||||
p_CClient_ProcessStringCmd = g_GameDll.FindPatternSIMD(reinterpret_cast<rsig_t>("\x48\x89\x6C\x24\x00\x57\x48\x81\xEC\x00\x00\x00\x00\x48\x8B\x7A\x20"), "xxxx?xxxx????xxxx");
|
||||
#endif // !GAMEDLL_S0 || !GAMEDLL_S1
|
||||
|
||||
v_CClient_Connect = p_CClient_Connect.RCast<bool (*)(CClient*, const char*, void*, bool, void*, char*, int)>(); /*48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 20 41 0F B6 E9*/
|
||||
v_CClient_Disconnect = p_CClient_Disconnect.RCast<bool (*)(CClient*, const Reputation_t, const char*, ...)>(); /*48 8B C4 4C 89 40 18 4C 89 48 20 53 56 57 48 81 EC ?? ?? ?? ?? 83 B9 ?? ?? ?? ?? ?? 49 8B F8 8B F2*/
|
||||
v_CClient_Clear = p_CClient_Clear.RCast<void (*)(CClient*)>(); /*40 53 41 56 41 57 48 83 EC 20 48 8B D9 48 89 74*/
|
||||
v_CClient_ProcessStringCmd = p_CClient_ProcessStringCmd.RCast<bool (*)(CClient*, NET_StringCmd*)>(); /*48 89 6C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 7A 20*/
|
||||
}
|
||||
virtual void GetVar(void) const
|
||||
{
|
||||
|
@ -1071,6 +1071,7 @@ playlists
|
||||
"DISCONNECT_SEND_RELIABLEOVERFLOW" "Connection to server overflowed (code:river)."
|
||||
"DISCONNECT_SEND_OVERFLOW" "Connection to server overflowed (code:dam)."
|
||||
"DISCONNECT_NETCHAN_OVERFLOW" "Connection to server overflowed (code:vulkan)."
|
||||
"DISCONNECT_STRINGCMD_OVERFLOW" "Connection to server overflowed (code:mountain)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,17 +31,23 @@ struct ServerPlayer_t
|
||||
ServerPlayer_t(void)
|
||||
: m_flCurrentNetProcessTime(0.0)
|
||||
, m_flLastNetProcessTime(0.0)
|
||||
, m_flStringCommandQuotaTimeStart(0.0)
|
||||
, m_nStringCommandQuotaCount(0)
|
||||
, m_bPersistenceEnabled(false)
|
||||
{}
|
||||
inline void Reset(void)
|
||||
{
|
||||
m_flCurrentNetProcessTime = 0.0;
|
||||
m_flLastNetProcessTime = 0.0;
|
||||
m_flStringCommandQuotaTimeStart = 0.0;
|
||||
m_nStringCommandQuotaCount = 0;
|
||||
m_bPersistenceEnabled = false;
|
||||
}
|
||||
|
||||
double m_flCurrentNetProcessTime;
|
||||
double m_flLastNetProcessTime;
|
||||
double m_flStringCommandQuotaTimeStart;
|
||||
int m_nStringCommandQuotaCount;
|
||||
bool m_bPersistenceEnabled;
|
||||
};
|
||||
|
||||
|
@ -105,7 +105,8 @@ void ConVar::Init(void) const
|
||||
sv_banlistRefreshInterval = ConVar::Create("sv_banlistRefreshInterval", "1.0", FCVAR_RELEASE, "Banlist refresh interval (seconds).", true, 1.f, false, 0.f, nullptr, nullptr);
|
||||
sv_statusRefreshInterval = ConVar::Create("sv_statusRefreshInterval" , "0.5", FCVAR_RELEASE, "Server status bar update interval (seconds).", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
|
||||
sv_autoReloadRate = ConVar::Create("sv_autoReloadRate" , "0", FCVAR_RELEASE, "Time in seconds between each server auto-reload (disabled if null). ", true, 0.f, false, 0.f, nullptr, nullptr);
|
||||
sv_autoReloadRate = ConVar::Create("sv_autoReloadRate" , "0", FCVAR_RELEASE, "Time in seconds between each server auto-reload (disabled if null). ", true, 0.f, false, 0.f, nullptr, nullptr);
|
||||
sv_quota_stringCmdsPerSecond = ConVar::Create("sv_quota_stringCmdsPerSecond", "16", FCVAR_RELEASE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands.", true, 0.f, false, 0.f, nullptr, nullptr);
|
||||
#ifdef DEDICATED
|
||||
sv_rcon_debug = ConVar::Create("sv_rcon_debug" , "0" , FCVAR_RELEASE, "Show rcon debug information ( !slower! ).", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
sv_rcon_sendlogs = ConVar::Create("sv_rcon_sendlogs" , "0" , FCVAR_RELEASE, "Network console logs to connected and authenticated sockets.", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
@ -203,7 +204,7 @@ void ConVar::Init(void) const
|
||||
net_tracePayload = ConVar::Create("net_tracePayload" , "0", FCVAR_DEVELOPMENTONLY , "Log the payload of the send/recv datagram to a file on the disk.", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
net_encryptionEnable = ConVar::Create("net_encryptionEnable" , "1", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED , "Use AES encryption on game packets.", false, 0.f, false, 0.f, nullptr, nullptr);
|
||||
net_useRandomKey = ConVar::Create("net_useRandomKey" , "1" , FCVAR_RELEASE , "Use random AES encryption key for game packets.", false, 0.f, false, 0.f, &NET_UseRandomKeyChanged_f, nullptr);
|
||||
net_processTimeBudget = ConVar::Create("net_processTimeBudget" ,"200" , FCVAR_RELEASE , "Net message process budget in milliseconds (removing netchannel if exceeded).", true, 0.f, false, 0.f, nullptr, "0 = disabled.");
|
||||
net_processTimeBudget = ConVar::Create("net_processTimeBudget" ,"150" , FCVAR_RELEASE , "Net message process budget in milliseconds (removing netchannel if exceeded).", true, 0.f, false, 0.f, nullptr, "0 = disabled.");
|
||||
//-------------------------------------------------------------------------
|
||||
// NETWORKSYSTEM |
|
||||
pylon_matchmaking_hostname = ConVar::Create("pylon_matchmaking_hostname", "ms.r5reloaded.com", FCVAR_RELEASE , "Holds the pylon matchmaking hostname.", false, 0.f, false, 0.f, &MP_HostName_Changed_f, nullptr);
|
||||
@ -263,6 +264,7 @@ void ConVar::InitShipped(void) const
|
||||
|
||||
#ifndef CLIENT_DLL
|
||||
ai_script_nodes_draw->SetValue(-1);
|
||||
bhit_enable->SetValue(0);
|
||||
#endif // !CLIENT_DLL
|
||||
#ifndef DEDICATED
|
||||
cl_threaded_bone_setup->RemoveFlags(FCVAR_DEVELOPMENTONLY);
|
||||
|
@ -69,6 +69,7 @@ ConVar* sv_statusRefreshInterval = nullptr;
|
||||
ConVar* sv_forceChatToTeamOnly = nullptr;
|
||||
|
||||
ConVar* sv_autoReloadRate = nullptr;
|
||||
ConVar* sv_quota_stringCmdsPerSecond = nullptr;
|
||||
|
||||
#ifdef DEDICATED
|
||||
ConVar* sv_rcon_debug = nullptr;
|
||||
@ -173,6 +174,7 @@ ConVar* net_encryptionEnable = nullptr;
|
||||
ConVar* net_useRandomKey = nullptr;
|
||||
ConVar* net_usesocketsforloopback = nullptr;
|
||||
ConVar* net_processTimeBudget = nullptr;
|
||||
|
||||
ConVar* pylon_matchmaking_hostname = nullptr;
|
||||
ConVar* pylon_host_update_interval = nullptr;
|
||||
ConVar* pylon_showdebuginfo = nullptr;
|
||||
|
@ -65,6 +65,7 @@ extern ConVar* sv_statusRefreshInterval;
|
||||
extern ConVar* sv_forceChatToTeamOnly;
|
||||
|
||||
extern ConVar* sv_autoReloadRate;
|
||||
extern ConVar* sv_quota_stringCmdsPerSecond;
|
||||
|
||||
#ifdef DEDICATED
|
||||
extern ConVar* sv_rcon_debug;
|
||||
@ -168,6 +169,7 @@ extern ConVar* net_encryptionEnable;
|
||||
extern ConVar* net_useRandomKey;
|
||||
extern ConVar* net_usesocketsforloopback;
|
||||
extern ConVar* net_processTimeBudget;
|
||||
|
||||
extern ConVar* pylon_matchmaking_hostname;
|
||||
extern ConVar* pylon_host_update_interval;
|
||||
extern ConVar* pylon_showdebuginfo;
|
||||
|
Loading…
x
Reference in New Issue
Block a user