fix(combatlog): enforce TBC attacker-state packet bounds

This commit is contained in:
Kelsi 2026-03-14 14:29:09 -07:00
parent 71e34e41b7
commit 4d4e5ed3b9

View file

@ -1399,7 +1399,14 @@ bool TbcPacketParsers::parseCastFailed(network::Packet& packet, CastFailedData&
// would mis-parse TBC's GUIDs and corrupt all subsequent damage fields. // would mis-parse TBC's GUIDs and corrupt all subsequent damage fields.
// ============================================================================ // ============================================================================
bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) { bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) {
if (packet.getSize() - packet.getReadPos() < 21) return false; data = AttackerStateUpdateData{};
const size_t startPos = packet.getReadPos();
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
// Fixed fields before sub-damage list:
// hitInfo(4) + attackerGuid(8) + targetGuid(8) + totalDamage(4) + subDamageCount(1) = 25 bytes
if (rem() < 25) return false;
data.hitInfo = packet.readUInt32(); data.hitInfo = packet.readUInt32();
data.attackerGuid = packet.readUInt64(); // full GUID in TBC data.attackerGuid = packet.readUInt64(); // full GUID in TBC
@ -1407,7 +1414,18 @@ bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Attacke
data.totalDamage = static_cast<int32_t>(packet.readUInt32()); data.totalDamage = static_cast<int32_t>(packet.readUInt32());
data.subDamageCount = packet.readUInt8(); data.subDamageCount = packet.readUInt8();
// Clamp to what can fit in the remaining payload (20 bytes per sub-damage entry).
const uint8_t maxSubDamageCount = static_cast<uint8_t>(std::min<size_t>(rem() / 20, 64));
if (data.subDamageCount > maxSubDamageCount) {
data.subDamageCount = maxSubDamageCount;
}
data.subDamages.reserve(data.subDamageCount);
for (uint8_t i = 0; i < data.subDamageCount; ++i) { for (uint8_t i = 0; i < data.subDamageCount; ++i) {
if (rem() < 20) {
packet.setReadPos(startPos);
return false;
}
SubDamage sub; SubDamage sub;
sub.schoolMask = packet.readUInt32(); sub.schoolMask = packet.readUInt32();
sub.damage = packet.readFloat(); sub.damage = packet.readFloat();
@ -1417,10 +1435,17 @@ bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Attacke
data.subDamages.push_back(sub); data.subDamages.push_back(sub);
} }
data.subDamageCount = static_cast<uint8_t>(data.subDamages.size());
// victimState + overkill are part of the expected payload.
if (rem() < 8) {
packet.setReadPos(startPos);
return false;
}
data.victimState = packet.readUInt32(); data.victimState = packet.readUInt32();
data.overkill = static_cast<int32_t>(packet.readUInt32()); data.overkill = static_cast<int32_t>(packet.readUInt32());
if (packet.getReadPos() < packet.getSize()) { if (rem() >= 4) {
data.blocked = packet.readUInt32(); data.blocked = packet.readUInt32();
} }