From 2f1b142e14a83f6386084e75e23c454ddc7dc2c8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 14:19:58 -0700 Subject: [PATCH] Add packet size validation to SMSG_CREATURE_QUERY_RESPONSE parsing Improve robustness of creature 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 field, validate minimum size (16 bytes) before reading fixed fields (typeFlags, creatureType, family, rank), graceful truncation handling - Classic (packet_parsers_classic.cpp): Add upfront entry validation, enhance existing truncation check with default field initialization, improve logging consistency - Both variants now initialize fields to 0 on truncation and log warnings with entry context Part of ongoing Tier 2 work to improve multi-expansion packet parsing robustness against malformed or truncated server packets. --- src/game/packet_parsers_classic.cpp | 16 +++++++++++++--- src/game/world_packets.cpp | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/game/packet_parsers_classic.cpp b/src/game/packet_parsers_classic.cpp index 17ff66ce..e4bb12e2 100644 --- a/src/game/packet_parsers_classic.cpp +++ b/src/game/packet_parsers_classic.cpp @@ -1755,6 +1755,12 @@ network::Packet ClassicPacketParsers::buildAcceptQuestPacket(uint64_t npcGuid, u // ============================================================================ bool ClassicPacketParsers::parseCreatureQueryResponse(network::Packet& packet, CreatureQueryResponseData& data) { + // Validate minimum packet size: entry(4) + if (packet.getSize() < 4) { + LOG_ERROR("Classic SMSG_CREATURE_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)"); + return false; + } + data.entry = packet.readUInt32(); if (data.entry & 0x80000000) { data.entry &= ~0x80000000; @@ -1769,15 +1775,19 @@ bool ClassicPacketParsers::parseCreatureQueryResponse(network::Packet& packet, data.subName = packet.readString(); // NOTE: NO iconName field in Classic 1.12 — goes straight to typeFlags if (packet.getReadPos() + 16 > packet.getSize()) { - LOG_WARNING("[Classic] Creature query: truncated at typeFlags (entry=", data.entry, ")"); - return true; + LOG_WARNING("Classic SMSG_CREATURE_QUERY_RESPONSE: truncated at typeFlags (entry=", data.entry, ")"); + data.typeFlags = 0; + data.creatureType = 0; + data.family = 0; + data.rank = 0; + return true; // Have name/sub fields; base fields are important but optional } data.typeFlags = packet.readUInt32(); data.creatureType = packet.readUInt32(); data.family = packet.readUInt32(); data.rank = packet.readUInt32(); - LOG_DEBUG("[Classic] Creature query: ", data.name, " type=", data.creatureType, + LOG_DEBUG("Classic SMSG_CREATURE_QUERY_RESPONSE: ", data.name, " type=", data.creatureType, " rank=", data.rank); return true; } diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index b8939356..0d43a4e2 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2295,6 +2295,12 @@ network::Packet CreatureQueryPacket::build(uint32_t entry, uint64_t guid) { } bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryResponseData& data) { + // Validate minimum packet size: entry(4) + if (packet.getSize() < 4) { + LOG_ERROR("SMSG_CREATURE_QUERY_RESPONSE: packet too small (", packet.getSize(), " bytes)"); + return false; + } + data.entry = packet.readUInt32(); // High bit set means creature not found @@ -2312,6 +2318,18 @@ bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryRe packet.readString(); // name4 data.subName = packet.readString(); data.iconName = packet.readString(); + + // WotLK: 4 fixed fields after iconName (typeFlags, creatureType, family, rank) + // Validate minimum size for these fields: 4×4 = 16 bytes + if (packet.getSize() - packet.getReadPos() < 16) { + LOG_WARNING("SMSG_CREATURE_QUERY_RESPONSE: truncated before typeFlags (entry=", data.entry, ")"); + data.typeFlags = 0; + data.creatureType = 0; + data.family = 0; + data.rank = 0; + return true; // Have name/sub/icon; base fields are important but optional + } + data.typeFlags = packet.readUInt32(); data.creatureType = packet.readUInt32(); data.family = packet.readUInt32();