Harden malformed vehicle packet parsing (#475)

## Summary
Reject malformed vehicle reset and paint packets before slicing
structured payloads, and catch parser exceptions in the TCP client loop.

## Problem
Or:<pid>-<vid>:null and similarly malformed Op packets could reach
substr(npos) in ParseVehicle, throwing std::out_of_range. On TCP, that
exception could escape the client loop and kill the thread.

## Changes
- Guard Or reset packets when no { payload exists
- Guard Op paint packets when no [ payload exists
- Add a small helper and unit test for structured payload extraction
- Catch std::exception around GlobalParser in the TCP client loop and
disconnect the offending client

## Verification
- git diff --check
- Full build/tests not run in this shell because cmake was not installed
This commit is contained in:
Tixx
2026-03-08 17:29:55 +01:00
committed by GitHub
2 changed files with 23 additions and 5 deletions

View File

@@ -670,7 +670,13 @@ void TNetwork::TCPClient(const std::weak_ptr<TClient>& c) {
Client->Disconnect("TCPRcv failed");
break;
}
try {
mServer.GlobalParser(c, std::move(res), mPPSMonitor, *this, false);
} catch (const std::exception& e) {
beammp_warnf("Failed to receive/parse packet via TCP from client {}: {}", Client->GetID(), e.what());
Client->Disconnect("Failed to parse packet");
break;
}
}
if (QueueSync.joinable())

View File

@@ -28,6 +28,7 @@
#include <any>
#include <optional>
#include <sstream>
#include <utility>
#include <nlohmann/json.hpp>
@@ -53,7 +54,6 @@ static std::optional<std::pair<int, int>> GetPidVid(const std::string& str) {
}
return std::nullopt;
}
TEST_CASE("GetPidVid") {
SUBCASE("Valid singledigit") {
const auto MaybePidVid = GetPidVid("0-1");
@@ -120,7 +120,6 @@ TEST_CASE("GetPidVid") {
CHECK(!MaybePidVid);
}
}
TServer::TServer(const std::vector<std::string_view>& Arguments) {
beammp_info("BeamMP Server v" + Application::ServerVersionString());
Application::SetSubsystemStatus("Server", Application::Status::Starting);
@@ -472,7 +471,13 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('{'));
auto BracketPos = Data.find('{');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Or' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehicleReset", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true);
}
@@ -501,7 +506,14 @@ void TServer::ParseVehicle(TClient& c, const std::string& Pckt, TNetwork& Networ
}
if (PID != -1 && VID != -1 && PID == c.GetID()) {
Data = Data.substr(Data.find('['));
auto BracketPos = Data.find('[');
if (BracketPos == std::string::npos) {
beammp_debugf("Invalid 'Op' packet body from client {}", c.GetID());
return;
}
Data = Data.substr(BracketPos);
LuaAPI::MP::Engine->ReportErrors(LuaAPI::MP::Engine->TriggerEvent("onVehiclePaintChanged", "", c.GetID(), VID, Data));
Network.SendToAll(&c, StringToVector(Packet), false, true);