fix(combatlog): validate packed guid bounds in classic spell cast parsers

This commit is contained in:
Kelsi 2026-03-14 10:25:07 -07:00
parent 69ff91e9a2
commit c9858655f6

View file

@ -358,8 +358,9 @@ bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartDa
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
if (rem() < 2) return false; if (rem() < 2) return false;
if (!hasFullPackedGuid(packet)) return false;
data.casterGuid = UpdateObjectParser::readPackedGuid(packet); data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
if (rem() < 1) return false; if (!hasFullPackedGuid(packet)) return false;
data.casterUnit = UpdateObjectParser::readPackedGuid(packet); data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
// uint8 castCount + uint32 spellId + uint16 castFlags + uint32 castTime = 11 bytes // uint8 castCount + uint32 spellId + uint16 castFlags + uint32 castTime = 11 bytes
@ -373,7 +374,7 @@ bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartDa
if (rem() < 2) return true; if (rem() < 2) return true;
uint16_t targetFlags = packet.readUInt16(); uint16_t targetFlags = packet.readUInt16();
// TARGET_FLAG_UNIT (0x02) or TARGET_FLAG_OBJECT (0x800) carry a packed GUID // TARGET_FLAG_UNIT (0x02) or TARGET_FLAG_OBJECT (0x800) carry a packed GUID
if (((targetFlags & 0x02) || (targetFlags & 0x800)) && rem() >= 1) { if (((targetFlags & 0x02) || (targetFlags & 0x800)) && hasFullPackedGuid(packet)) {
data.targetGuid = UpdateObjectParser::readPackedGuid(packet); data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
} }
@ -398,8 +399,9 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); }; auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
if (rem() < 2) return false; if (rem() < 2) return false;
if (!hasFullPackedGuid(packet)) return false;
data.casterGuid = UpdateObjectParser::readPackedGuid(packet); data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
if (rem() < 1) return false; if (!hasFullPackedGuid(packet)) return false;
data.casterUnit = UpdateObjectParser::readPackedGuid(packet); data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
// uint8 castCount + uint32 spellId + uint16 castFlags = 7 bytes // uint8 castCount + uint32 spellId + uint16 castFlags = 7 bytes
@ -416,16 +418,21 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
} }
const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128); const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128);
data.hitTargets.reserve(storedHitLimit); data.hitTargets.reserve(storedHitLimit);
for (uint16_t i = 0; i < rawHitCount && rem() >= 1; ++i) { uint16_t parsedHitCount = 0;
for (uint16_t i = 0; i < rawHitCount; ++i) {
if (!hasFullPackedGuid(packet)) {
break;
}
const uint64_t targetGuid = UpdateObjectParser::readPackedGuid(packet); const uint64_t targetGuid = UpdateObjectParser::readPackedGuid(packet);
++parsedHitCount;
if (i < storedHitLimit) { if (i < storedHitLimit) {
data.hitTargets.push_back(targetGuid); data.hitTargets.push_back(targetGuid);
} }
} }
data.hitCount = static_cast<uint8_t>(data.hitTargets.size()); data.hitCount = static_cast<uint8_t>(data.hitTargets.size());
// Check if we read all expected hits // Check if we read all expected hits
if (data.hitTargets.size() < rawHitCount) { if (parsedHitCount < rawHitCount) {
LOG_WARNING("[Classic] Spell go: truncated hit targets at index ", (int)data.hitTargets.size(), LOG_WARNING("[Classic] Spell go: truncated hit targets at index ", (int)parsedHitCount,
"/", (int)rawHitCount); "/", (int)rawHitCount);
} }
@ -437,7 +444,11 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
} }
const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128); const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128);
data.missTargets.reserve(storedMissLimit); data.missTargets.reserve(storedMissLimit);
for (uint16_t i = 0; i < rawMissCount && rem() >= 2; ++i) { uint16_t parsedMissCount = 0;
for (uint16_t i = 0; i < rawMissCount; ++i) {
if (!hasFullPackedGuid(packet)) {
break;
}
SpellGoMissEntry m; SpellGoMissEntry m;
m.targetGuid = UpdateObjectParser::readPackedGuid(packet); m.targetGuid = UpdateObjectParser::readPackedGuid(packet);
if (rem() < 1) break; if (rem() < 1) break;
@ -447,14 +458,15 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
(void)packet.readUInt32(); (void)packet.readUInt32();
(void)packet.readUInt8(); (void)packet.readUInt8();
} }
++parsedMissCount;
if (i < storedMissLimit) { if (i < storedMissLimit) {
data.missTargets.push_back(m); data.missTargets.push_back(m);
} }
} }
data.missCount = static_cast<uint8_t>(data.missTargets.size()); data.missCount = static_cast<uint8_t>(data.missTargets.size());
// Check if we read all expected misses // Check if we read all expected misses
if (data.missTargets.size() < rawMissCount) { if (parsedMissCount < rawMissCount) {
LOG_WARNING("[Classic] Spell go: truncated miss targets at index ", (int)data.missTargets.size(), LOG_WARNING("[Classic] Spell go: truncated miss targets at index ", (int)parsedMissCount,
"/", (int)rawMissCount); "/", (int)rawMissCount);
} }