diff options
author | Shauren <shauren.trinity@gmail.com> | 2023-06-29 11:39:02 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2023-06-29 11:39:02 +0200 |
commit | 29eac37a16df2ec14cba89f4d6e28f54ca1a4e25 (patch) | |
tree | 0e340300bd3e1e0aaf8ff27324434b59f6a3e2d2 /src | |
parent | bacfbec25180cd0a02fec20e788d74e2fcaf1a0c (diff) |
Core/Spells: Fully prevent infinite proc loops and add logging to detect most spells that could possibly trigger this behavior at startup
Closes #28865
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 4 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.cpp | 1 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.h | 3 | ||||
-rw-r--r-- | src/server/game/Spells/SpellMgr.cpp | 21 |
5 files changed, 41 insertions, 2 deletions
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index cd97d0d4081..46b0f9bc681 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -303,7 +303,7 @@ SpellNonMeleeDamage::SpellNonMeleeDamage(Unit* _attacker, Unit* _target, SpellIn Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), m_lastSanctuaryTime(0), LastCharmerGUID(), movespline(new Movement::MoveSpline()), - m_ControlledByPlayer(false), m_procDeep(0), m_transformSpell(0), + m_ControlledByPlayer(false), m_procDeep(0), m_procChainLength(0), m_transformSpell(0), m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), m_unitMovedByMe(nullptr), m_playerMovingMe(nullptr), m_charmer(nullptr), m_charmed(nullptr), i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_vehicle(nullptr), @@ -5320,6 +5320,13 @@ void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage const* log) ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo) { + static constexpr int32 ProcChainHardLimit = 10; + if (spell && spell->GetProcChainLength() >= ProcChainHardLimit) + { + TC_LOG_ERROR("spells.aura.effect", "Unit::ProcSkillsAndAuras: Possible infinite proc loop detected, current triggering spell {}", spell->GetDebugInfo().c_str()); + return; + } + WeaponAttackType attType = damageInfo ? damageInfo->GetAttackType() : BASE_ATTACK; if (typeMaskActor && actor) actor->ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); @@ -10020,6 +10027,9 @@ void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProc { Spell const* triggeringSpell = eventInfo.GetProcSpell(); bool const disableProcs = triggeringSpell && triggeringSpell->IsProcDisabled(); + + int32 oldProcChainLength = std::exchange(m_procChainLength, std::max(m_procChainLength + 1, triggeringSpell ? triggeringSpell->GetProcChainLength() : 0)); + if (disableProcs) SetCantProc(true); @@ -10033,6 +10043,8 @@ void Unit::TriggerAurasProcOnEvent(ProcEventInfo& eventInfo, AuraApplicationProc if (disableProcs) SetCantProc(false); + + m_procChainLength = oldProcChainLength; } ///----------Pet responses methods----------------- diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index e01d32067e5..0f63cc369a9 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1803,6 +1803,7 @@ class TC_GAME_API Unit : public WorldObject // proc trigger system bool CanProc() const { return !m_procDeep; } void SetCantProc(bool apply); + int32 GetProcChainLength() const { return m_procChainLength; } uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const; @@ -1950,7 +1951,8 @@ class TC_GAME_API Unit : public WorldObject DeathState m_deathState; - int32 m_procDeep; + int32 m_procDeep; // tracked for proc system correctness (what spells should proc what) + int32 m_procChainLength; // tracked to protect against infinite proc loops (hard limit, will disallow procs even if they should happen) typedef std::list<DynamicObject*> DynObjectList; DynObjectList m_dynObj; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index ba4ac81046f..4029dfdcfa0 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -584,6 +584,7 @@ m_spellValue(new SpellValue(m_spellInfo, caster)), _spellEvent(nullptr) memset(m_misc.Raw.Data, 0, sizeof(m_misc.Raw.Data)); m_SpellVisual.SpellXSpellVisualID = caster->GetCastSpellXSpellVisualId(m_spellInfo); m_triggeredByAuraSpell = nullptr; + m_procChainLength = caster->IsUnit() ? caster->ToUnit()->GetProcChainLength() : 0; _spellAura = nullptr; _dynObjAura = nullptr; diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 3ebadc1c125..52b57d48d7c 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -607,6 +607,8 @@ class TC_GAME_API Spell bool IsTriggeredByAura(SpellInfo const* auraSpellInfo) const { return (auraSpellInfo == m_triggeredByAuraSpell); } + int32 GetProcChainLength() const { return m_procChainLength; } + bool IsDeletable() const { return !m_referencedFromCurrentSpell && !m_executedCurrently; } void SetReferencedFromCurrent(bool yes) { m_referencedFromCurrentSpell = yes; } bool IsInterruptable() const { return !m_executedCurrently; } @@ -895,6 +897,7 @@ class TC_GAME_API Spell // we can't store original aura link to prevent access to deleted auras // and in same time need aura data and after aura deleting. SpellInfo const* m_triggeredByAuraSpell; + int32 m_procChainLength; std::unique_ptr<PathGenerator> m_preGeneratedPath; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 5e39db72916..c4279567f70 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -1813,6 +1813,7 @@ void SpellMgr::LoadSpellProcs() procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent + bool triggersSpell = false; for (SpellEffectInfo const& spellEffectInfo : spellInfo.GetEffects()) { if (!spellEffectInfo.IsAura()) @@ -1838,6 +1839,10 @@ void SpellMgr::LoadSpellProcs() if (spellEffectInfo.CalcValue() <= -100) procEntry.HitMask = PROC_HIT_MISS; break; + case SPELL_AURA_PROC_TRIGGER_SPELL: + case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE: + triggersSpell = spellEffectInfo.TriggerSpell != 0; + break; default: continue; } @@ -1856,6 +1861,22 @@ void SpellMgr::LoadSpellProcs() procEntry.Cooldown = Milliseconds(spellInfo.ProcCooldown); procEntry.Charges = spellInfo.ProcCharges; + if (spellInfo.HasAttribute(SPELL_ATTR3_CAN_PROC_FROM_PROCS) && !procEntry.SpellFamilyMask + && procEntry.Chance >= 100 + && spellInfo.ProcBasePPM <= 0.0f + && procEntry.Cooldown <= 0ms + && procEntry.Charges <= 0 + && procEntry.ProcFlags & (PROC_FLAG_DEAL_MELEE_ABILITY | PROC_FLAG_DEAL_RANGED_ATTACK | PROC_FLAG_DEAL_RANGED_ABILITY | PROC_FLAG_DEAL_HELPFUL_ABILITY + | PROC_FLAG_DEAL_HARMFUL_ABILITY | PROC_FLAG_DEAL_HELPFUL_SPELL | PROC_FLAG_DEAL_HARMFUL_SPELL | PROC_FLAG_DEAL_HARMFUL_PERIODIC + | PROC_FLAG_DEAL_HELPFUL_PERIODIC) + && triggersSpell) + { + TC_LOG_ERROR("sql.sql", "Spell Id {} has SPELL_ATTR3_CAN_PROC_FROM_PROCS attribute and no restriction on what spells can cause it to proc and no cooldown. " + "This spell can cause infinite proc loops. Proc data for this spell was not generated, data in `spell_proc` table is required for it to function!", + spellInfo.Id); + continue; + } + mSpellProcMap[{ spellInfo.Id, spellInfo.Difficulty }] = procEntry; ++count; } |