aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2023-06-29 11:39:02 +0200
committerShauren <shauren.trinity@gmail.com>2023-06-29 11:39:02 +0200
commit29eac37a16df2ec14cba89f4d6e28f54ca1a4e25 (patch)
tree0e340300bd3e1e0aaf8ff27324434b59f6a3e2d2 /src
parentbacfbec25180cd0a02fec20e788d74e2fcaf1a0c (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.cpp14
-rw-r--r--src/server/game/Entities/Unit/Unit.h4
-rw-r--r--src/server/game/Spells/Spell.cpp1
-rw-r--r--src/server/game/Spells/Spell.h3
-rw-r--r--src/server/game/Spells/SpellMgr.cpp21
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;
}