Add packet size validation to SMSG_GAMEOBJECT_QUERY_RESPONSE parsing

Improve robustness of game object query response parsing by adding defensive
size checks to both WotLK/TBC and Classic variants:

- WotLK/TBC (world_packets.cpp): Add upfront validation for entry, type,
  displayId fields, and improved in-loop handling for variable-length data
  array with partial data graceful degradation
- Classic (packet_parsers_classic.cpp): Add upfront validation for entry,
  type, displayId fields, and enhanced in-loop data array read with
  truncation detection
- Both variants now log warnings when data fields are truncated

Part of ongoing Tier 2 work to improve multi-expansion packet parsing robustness
against malformed or truncated server packets.
This commit is contained in:
Kelsi 2026-03-11 14:11:45 -07:00
parent d1414b6a46
commit 73abbc2a08
2 changed files with 44 additions and 0 deletions

View file

@ -979,6 +979,12 @@ bool ClassicPacketParsers::parseGuildQueryResponse(network::Packet& packet, Guil
// ============================================================================
bool ClassicPacketParsers::parseGameObjectQueryResponse(network::Packet& packet, GameObjectQueryResponseData& data) {
// Validate minimum packet size: entry(4)
if (packet.getSize() < 4) {
LOG_ERROR("Classic SMSG_GAMEOBJECT_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)");
return false;
}
data.entry = packet.readUInt32();
// High bit set means gameobject not found
@ -988,6 +994,12 @@ bool ClassicPacketParsers::parseGameObjectQueryResponse(network::Packet& packet,
return true;
}
// Validate minimum size for fixed fields: type(4) + displayId(4)
if (packet.getSize() - packet.getReadPos() < 8) {
LOG_ERROR("Classic SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
return false;
}
data.type = packet.readUInt32();
data.displayId = packet.readUInt32();
// 4 name strings
@ -1003,6 +1015,16 @@ bool ClassicPacketParsers::parseGameObjectQueryResponse(network::Packet& packet,
data.data[i] = packet.readUInt32();
}
data.hasData = true;
} else if (remaining > 0) {
// Partial data field; read what we can
uint32_t fieldsToRead = remaining / 4;
for (uint32_t i = 0; i < fieldsToRead && i < 24; i++) {
data.data[i] = packet.readUInt32();
}
if (fieldsToRead < 24) {
LOG_WARNING("Classic SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated in data fields (", fieldsToRead,
" of 24 read, entry=", data.entry, ")");
}
}
if (data.type == 15) { // MO_TRANSPORT

View file

@ -2336,6 +2336,12 @@ network::Packet GameObjectQueryPacket::build(uint32_t entry, uint64_t guid) {
}
bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQueryResponseData& data) {
// Validate minimum packet size: entry(4)
if (packet.getSize() < 4) {
LOG_ERROR("SMSG_GAMEOBJECT_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)");
return false;
}
data.entry = packet.readUInt32();
// High bit set means gameobject not found
@ -2346,6 +2352,12 @@ bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQue
return true;
}
// Validate minimum size for fixed fields: type(4) + displayId(4)
if (packet.getSize() - packet.getReadPos() < 8) {
LOG_ERROR("SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated before names (entry=", data.entry, ")");
return false;
}
data.type = packet.readUInt32(); // GameObjectType
data.displayId = packet.readUInt32();
// 4 name strings (only first is usually populated)
@ -2367,6 +2379,16 @@ bool GameObjectQueryResponseParser::parse(network::Packet& packet, GameObjectQue
data.data[i] = packet.readUInt32();
}
data.hasData = true;
} else if (remaining > 0) {
// Partial data field; read what we can
uint32_t fieldsToRead = remaining / 4;
for (uint32_t i = 0; i < fieldsToRead && i < 24; i++) {
data.data[i] = packet.readUInt32();
}
if (fieldsToRead < 24) {
LOG_WARNING("SMSG_GAMEOBJECT_QUERY_RESPONSE: truncated in data fields (", fieldsToRead,
" of 24 read, entry=", data.entry, ")");
}
}
LOG_DEBUG("GameObject query response: ", data.name, " (type=", data.type, " entry=", data.entry, ")");