Cap hit/miss counts in Classic and TBC spell parsers

Add DoS protection to Classic and TBC parseSpellGo implementations:
- Cap hitCount and missCount to 128 each (prevents OOM from huge arrays)
- Track actual reads vs expected counts
- Log truncation warnings with index information
- Graceful truncation with count updates

Ensures consistent hardening across all expansion variants (Vanilla/TBC/WotLK).
This commit is contained in:
Kelsi 2026-03-11 14:29:37 -07:00
parent 164124783b
commit 7034bc5f63
2 changed files with 44 additions and 0 deletions

View file

@ -391,14 +391,30 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
// Hit targets
if (rem() < 1) return true;
data.hitCount = packet.readUInt8();
// Cap hit count to prevent OOM from huge target lists
if (data.hitCount > 128) {
LOG_WARNING("[Classic] Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
data.hitCount = 128;
}
data.hitTargets.reserve(data.hitCount);
for (uint8_t i = 0; i < data.hitCount && rem() >= 1; ++i) {
data.hitTargets.push_back(UpdateObjectParser::readPackedGuid(packet));
}
// Check if we read all expected hits
if (data.hitTargets.size() < data.hitCount) {
LOG_WARNING("[Classic] Spell go: truncated hit targets at index ", (int)data.hitTargets.size(),
"/", (int)data.hitCount);
data.hitCount = data.hitTargets.size();
}
// Miss targets
if (rem() < 1) return true;
data.missCount = packet.readUInt8();
// Cap miss count to prevent OOM
if (data.missCount > 128) {
LOG_WARNING("[Classic] Spell go: missCount capped (requested=", (int)data.missCount, ")");
data.missCount = 128;
}
data.missTargets.reserve(data.missCount);
for (uint8_t i = 0; i < data.missCount && rem() >= 2; ++i) {
SpellGoMissEntry m;
@ -407,6 +423,12 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
m.missType = packet.readUInt8();
data.missTargets.push_back(m);
}
// Check if we read all expected misses
if (data.missTargets.size() < data.missCount) {
LOG_WARNING("[Classic] Spell go: truncated miss targets at index ", (int)data.missTargets.size(),
"/", (int)data.missCount);
data.missCount = data.missTargets.size();
}
LOG_DEBUG("[Classic] Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,
" misses=", (int)data.missCount);

View file

@ -1276,13 +1276,29 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data)
}
data.hitCount = packet.readUInt8();
// Cap hit count to prevent OOM from huge target lists
if (data.hitCount > 128) {
LOG_WARNING("[TBC] Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
data.hitCount = 128;
}
data.hitTargets.reserve(data.hitCount);
for (uint8_t i = 0; i < data.hitCount && packet.getReadPos() + 8 <= packet.getSize(); ++i) {
data.hitTargets.push_back(packet.readUInt64()); // full GUID in TBC
}
// Check if we read all expected hits
if (data.hitTargets.size() < data.hitCount) {
LOG_WARNING("[TBC] Spell go: truncated hit targets at index ", (int)data.hitTargets.size(),
"/", (int)data.hitCount);
data.hitCount = data.hitTargets.size();
}
if (packet.getReadPos() < packet.getSize()) {
data.missCount = packet.readUInt8();
// Cap miss count to prevent OOM
if (data.missCount > 128) {
LOG_WARNING("[TBC] Spell go: missCount capped (requested=", (int)data.missCount, ")");
data.missCount = 128;
}
data.missTargets.reserve(data.missCount);
for (uint8_t i = 0; i < data.missCount && packet.getReadPos() + 9 <= packet.getSize(); ++i) {
SpellGoMissEntry m;
@ -1290,6 +1306,12 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data)
m.missType = packet.readUInt8();
data.missTargets.push_back(m);
}
// Check if we read all expected misses
if (data.missTargets.size() < data.missCount) {
LOG_WARNING("[TBC] Spell go: truncated miss targets at index ", (int)data.missTargets.size(),
"/", (int)data.missCount);
data.missCount = data.missTargets.size();
}
}
LOG_DEBUG("[TBC] Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,