Skip to content

Commit 557a25e

Browse files
committed
fix(RCE): patches various RCE exploits present in QoS
1 parent 6b7ca23 commit 557a25e

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

src/component/engine/patches/patches.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,27 @@ namespace patches
3333

3434
namespace
3535
{
36+
constexpr std::size_t k_huffman_max_decoded_bytes = 0x20000;
37+
constexpr std::size_t k_huffman_max_compressed_bytes = k_huffman_max_decoded_bytes / 8;
38+
constexpr std::size_t k_ui_replace_directive_max_len = 0x100;
39+
constexpr std::size_t k_party_member_join_max_message_bytes = 0x4000;
40+
41+
std::size_t bounded_length(const char* value, const std::size_t max_len)
42+
{
43+
if (!value)
44+
{
45+
return 0;
46+
}
47+
48+
std::size_t length = 0;
49+
while (length < max_len && value[length] != '\0')
50+
{
51+
++length;
52+
}
53+
54+
return length;
55+
}
56+
3657
std::string build_shortversion_string()
3758
{
3859
std::string version = VERSION_PRODUCT;
@@ -90,6 +111,85 @@ namespace patches
90111
return 1;
91112
}
92113

114+
using cl_parse_server_message_huffman_t = unsigned int(__cdecl*)(int, _DWORD*);
115+
utils::hook::detour cl_parse_server_message_huffman_hook;
116+
unsigned int __cdecl CL_ParseServerMessage_huffman_guard(int a1, _DWORD* a2)
117+
{
118+
const auto* const original = reinterpret_cast<cl_parse_server_message_huffman_t>(cl_parse_server_message_huffman_hook.get_original());
119+
120+
if (!a2)
121+
{
122+
return original(a1, a2);
123+
}
124+
125+
if (a2[5] < a2[7])
126+
{
127+
game::Com_Error(
128+
(int)".\\cl_parse_mp.cpp",
129+
1243,
130+
1,
131+
(char*)"Huffman compressed msg cursor underflow detected\n");
132+
return 0;
133+
}
134+
135+
const auto compressed_bytes = static_cast<std::size_t>(a2[5] - a2[7]);
136+
if (compressed_bytes > k_huffman_max_compressed_bytes)
137+
{
138+
game::Com_Error(
139+
(int)".\\cl_parse_mp.cpp",
140+
1243,
141+
1,
142+
(char*)"Huffman compressed msg exceeded safe decode limit (%zu > %zu)\n",
143+
compressed_bytes,
144+
k_huffman_max_compressed_bytes);
145+
return 0;
146+
}
147+
148+
return original(a1, a2);
149+
}
150+
151+
using ui_replace_directive_t = char*(__fastcall*)(int, char*, int, unsigned __int8);
152+
utils::hook::detour ui_replace_directive_hook;
153+
char* __fastcall UI_ReplaceDirective_guard(int ArgList, char* a2, int a3, unsigned __int8 a4)
154+
{
155+
const auto* const original = reinterpret_cast<ui_replace_directive_t>(ui_replace_directive_hook.get_original());
156+
const auto* const arg_list = reinterpret_cast<const char*>(ArgList);
157+
if (bounded_length(arg_list, k_ui_replace_directive_max_len + 1) > k_ui_replace_directive_max_len
158+
|| bounded_length(a2, k_ui_replace_directive_max_len + 1) > k_ui_replace_directive_max_len)
159+
{
160+
game::Com_Printf(0, "UI_ReplaceDirective: rejected oversized directive input\n");
161+
return a2;
162+
}
163+
164+
return original(ArgList, a2, a3, a4);
165+
}
166+
167+
using party_atomic_host_handle_member_join_t = int(__cdecl*)(char, _DWORD*, int, __int64, int, _DWORD*);
168+
utils::hook::detour party_atomic_host_handle_member_join_hook;
169+
int __cdecl PartyAtomicHost_HandleMemberJoin_guard(char a1, _DWORD* a2, int a3, __int64 a4, int a5, _DWORD* a6)
170+
{
171+
const auto* const original = reinterpret_cast<party_atomic_host_handle_member_join_t>(party_atomic_host_handle_member_join_hook.get_original());
172+
if (!a2 || !a6)
173+
{
174+
return original(a1, a2, a3, a4, a5, a6);
175+
}
176+
177+
if (a6[5] < a6[7])
178+
{
179+
game::Com_Printf(0, "PartyAtomicHost_HandleMemberJoin: rejected malformed message cursor\n");
180+
return 0;
181+
}
182+
183+
const auto unread_bytes = static_cast<std::size_t>(a6[5] - a6[7]);
184+
if (unread_bytes > k_party_member_join_max_message_bytes)
185+
{
186+
game::Com_Printf(0, "PartyAtomicHost_HandleMemberJoin: rejected oversized message (%zu bytes)\n", unread_bytes);
187+
return 0;
188+
}
189+
190+
return original(a1, a2, a3, a4, a5, a6);
191+
}
192+
93193
bool dvar_enabled(const char* name)
94194
{
95195
const auto* const dvar = game::Dvar_FindVar(name);
@@ -497,6 +597,10 @@ namespace patches
497597
#endif
498598
}, scheduler::main);
499599

600+
cl_parse_server_message_huffman_hook.create(game::game_offset(0x1030D960), CL_ParseServerMessage_huffman_guard);
601+
ui_replace_directive_hook.create(game::game_offset(0x102BB870), UI_ReplaceDirective_guard);
602+
party_atomic_host_handle_member_join_hook.create(game::game_offset(0x103087B0), PartyAtomicHost_HandleMemberJoin_guard);
603+
500604
scheduler::loop([]
501605
{
502606
make_dvar_saved_and_writable("sv_cheats");

0 commit comments

Comments
 (0)