From 68b3cef0fe2c11811d4039e284e18770ca69c8d0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 14:30:57 -0700 Subject: [PATCH] Harden AuraUpdateParser against malformed packets WotLK SMSG_AURA_UPDATE (3.3.5a) improvements: - Cap entry count to 512 (isAll) or 1 (single) to prevent unbounded loop DoS - Validate 5-byte minimum before each slot+spellId read - Validate 3-byte minimum before flags/level/charges read - Validate space before casterGuid packed GUID read - Validate 8-byte minimum before duration field reads - Validate 4-byte minimum before each effect amount read - Graceful truncation with field initialization and partial read support - Log all truncation events with entry index information Prevents DoS and undefined behavior from high-frequency aura update packets. --- src/game/world_packets.cpp | 61 +++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 8d82e330..93fd167f 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -3288,34 +3288,71 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) { } bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool isAll) { + // Validation: packed GUID (1-8 bytes minimum for reading) + if (packet.getSize() - packet.getReadPos() < 1) return false; + data.guid = UpdateObjectParser::readPackedGuid(packet); - while (packet.getReadPos() < packet.getSize()) { + // Cap number of aura entries to prevent unbounded loop DoS + uint32_t maxAuras = isAll ? 512 : 1; + uint32_t auraCount = 0; + + while (packet.getReadPos() < packet.getSize() && auraCount < maxAuras) { + // Validate we can read slot (1) + spellId (4) = 5 bytes minimum + if (packet.getSize() - packet.getReadPos() < 5) { + LOG_DEBUG("Aura update: truncated entry at position ", auraCount); + break; + } + uint8_t slot = packet.readUInt8(); uint32_t spellId = packet.readUInt32(); + auraCount++; AuraSlot aura; if (spellId != 0) { aura.spellId = spellId; - aura.flags = packet.readUInt8(); - aura.level = packet.readUInt8(); - aura.charges = packet.readUInt8(); - if (!(aura.flags & 0x08)) { // NOT_CASTER flag - aura.casterGuid = UpdateObjectParser::readPackedGuid(packet); + // Validate flags + level + charges (3 bytes) + if (packet.getSize() - packet.getReadPos() < 3) { + LOG_WARNING("Aura update: truncated flags/level/charges at entry ", auraCount); + aura.flags = 0; + aura.level = 0; + aura.charges = 0; + } else { + aura.flags = packet.readUInt8(); + aura.level = packet.readUInt8(); + aura.charges = packet.readUInt8(); } - if (aura.flags & 0x20) { // DURATION - aura.maxDurationMs = static_cast(packet.readUInt32()); - aura.durationMs = static_cast(packet.readUInt32()); + if (!(aura.flags & 0x08)) { // NOT_CASTER flag + // Validate space for packed GUID read (minimum 1 byte) + if (packet.getSize() - packet.getReadPos() < 1) { + aura.casterGuid = 0; + } else { + aura.casterGuid = UpdateObjectParser::readPackedGuid(packet); + } + } + + if (aura.flags & 0x20) { // DURATION - need 8 bytes (two uint32s) + if (packet.getSize() - packet.getReadPos() < 8) { + LOG_WARNING("Aura update: truncated duration fields at entry ", auraCount); + aura.maxDurationMs = 0; + aura.durationMs = 0; + } else { + aura.maxDurationMs = static_cast(packet.readUInt32()); + aura.durationMs = static_cast(packet.readUInt32()); + } } if (aura.flags & 0x40) { // EFFECT_AMOUNTS // Only read amounts for active effect indices (flags 0x01, 0x02, 0x04) for (int i = 0; i < 3; ++i) { if (aura.flags & (1 << i)) { - if (packet.getReadPos() < packet.getSize()) { + if (packet.getSize() - packet.getReadPos() >= 4) { packet.readUInt32(); + } else { + LOG_WARNING("Aura update: truncated effect amount ", i, " at entry ", auraCount); + break; } } } @@ -3328,6 +3365,10 @@ bool AuraUpdateParser::parse(network::Packet& packet, AuraUpdateData& data, bool if (!isAll) break; } + if (auraCount >= maxAuras && packet.getReadPos() < packet.getSize()) { + LOG_WARNING("Aura update: capped at ", maxAuras, " entries, remaining data ignored"); + } + LOG_DEBUG("Aura update for 0x", std::hex, data.guid, std::dec, ": ", data.updates.size(), " slots"); return true;