Summary
Eluna::GetBinding<T>() is written to return nullptr, but the returned pointer is dereferenced without a null check in 52 call sites (AzerothCore build). This crashed our production server.
Crash
AzerothCore 3.3.5a, rev 007b4464f5d2 (2026-07-22), ~50 concurrent players. Backtrace from a core dump:
#0 std::_Hashtable<EventKey<Hooks::ServerEvents>, ...>::empty (this=0x18)
#1 std::unordered_map<...>::empty (this=0x18)
#2 BindingMap<EventKey<Hooks::ServerEvents>>::HasBindingsFor (this=0x0, key=...)
at src/server/game/LuaEngine/BindingMap.h:165
#3 Eluna::OnPacketSendAny (this=0x715637fc0280, player=0x7155c67f4c00, packet=..., result=@0x7156255fb297: true)
at src/server/game/LuaEngine/hooks/PacketHooks.cpp:40
#4 Eluna::OnPacketSend (this=0x715637fc0280, session=<optimized out>, packet=...)
at src/server/game/LuaEngine/hooks/PacketHooks.cpp:34
#5 WorldSession::SendPacket (this=0x7155a135c100, packet=0x7156255fb2e8)
at src/server/game/Server/WorldSession.cpp:351
#6 Group::UpdatePlayerOutOfRange (this=<optimized out>, player=0x71554076d7c0)
at src/server/game/Groups/Group.cpp:1888
#7 Player::SendUpdateToOutOfRangeGroupMembers (this=0x71554076d7c0)
at src/server/game/Entities/Player/Player.cpp:11946
#8 Player::Update (this=0x71554076d7c0, p_time=1)
at src/server/game/Entities/Player/PlayerUpdates.cpp:391
#9 Map::Update (this=0x715635914300, t_diff=100, s_diff=1)
at src/server/game/Maps/Map.cpp:503
BindingMap::HasBindingsFor is called with this=0x0.
Worth noting: no Lua script on our server registers any packet event. The hook that crashed the server does nothing for us — it fires on every outgoing packet only to check a binding map that happened to be null.
Root cause
LuaEngine.h:392 — GetBinding() has two explicit return nullptr paths:
template<typename T>
BindingMap<T>* GetBinding(std::underlying_type_t<Hooks::RegisterTypes> type)
{
if (type >= Hooks::REGTYPE_COUNT)
return nullptr;
auto& binding = bindingMaps[type];
if (!binding)
return nullptr;
return dynamic_cast<BindingMap<T>*>(binding.get());
}
dynamic_cast can also yield nullptr on type mismatch.
Every START_HOOK* macro then does:
auto binding = GetBinding<...>(REGTYPE_...);
auto key = ...;
if (!binding->HasBindingsFor(key)) // <-- unchecked dereference
return;
Scope
Call sites that dereference the result without a null check, in the code actually compiled for AzerothCore:
| Location |
Sites |
hooks/ (13 files) |
27 |
methods/AzerothCore/GlobalMethods.h |
16 |
LuaEngine.cpp |
9 |
| Total |
52 |
The same GlobalMethods.h pattern is duplicated per core, so methods/{Mangos,CMangos,VMangos,TrinityCore} add 16 sites each — roughly 116 sites across all supported cores.
Suggested fix
Guard at the call sites (what we did locally, 52 changes, running in production):
if (!binding || !binding->HasBindingsFor(key))
return;
Alternatively — and probably better — have the START_HOOK* macros do the null check once, so new hooks cannot reintroduce the bug. A third option is returning a reference to a shared empty BindingMap instead of nullptr, which removes the failure mode entirely.
Reproduction
We could not reproduce on demand: it happened once in ~17 hours of uptime with ~50 players. The core dump backtrace above is the evidence.
Happy to test a patch against a live server.
Summary
Eluna::GetBinding<T>()is written to returnnullptr, but the returned pointer is dereferenced without a null check in 52 call sites (AzerothCore build). This crashed our production server.Crash
AzerothCore 3.3.5a, rev
007b4464f5d2(2026-07-22), ~50 concurrent players. Backtrace from a core dump:BindingMap::HasBindingsForis called withthis=0x0.Worth noting: no Lua script on our server registers any packet event. The hook that crashed the server does nothing for us — it fires on every outgoing packet only to check a binding map that happened to be null.
Root cause
LuaEngine.h:392—GetBinding()has two explicitreturn nullptrpaths:dynamic_castcan also yieldnullptron type mismatch.Every
START_HOOK*macro then does:Scope
Call sites that dereference the result without a null check, in the code actually compiled for AzerothCore:
hooks/(13 files)methods/AzerothCore/GlobalMethods.hLuaEngine.cppThe same
GlobalMethods.hpattern is duplicated per core, somethods/{Mangos,CMangos,VMangos,TrinityCore}add 16 sites each — roughly 116 sites across all supported cores.Suggested fix
Guard at the call sites (what we did locally, 52 changes, running in production):
Alternatively — and probably better — have the
START_HOOK*macros do the null check once, so new hooks cannot reintroduce the bug. A third option is returning a reference to a shared emptyBindingMapinstead ofnullptr, which removes the failure mode entirely.Reproduction
We could not reproduce on demand: it happened once in ~17 hours of uptime with ~50 players. The core dump backtrace above is the evidence.
Happy to test a patch against a live server.