16
0x1337c0de / Entity List Management in Q2 Mods
« on: March 20, 2012, 10:20:04 PM »
Several extensive attempts were made to crash the Loxophilia server via the entity list crash pathway. These attempts did not succeed. The crash is produced when the active entity list is filled and a subseqent call to G_Spawn in the game mod can't find space in the entity list to instantiate a new entity. The function fails with a call to gi.error ("ED_Alloc: no free edicts"); which halts the game server resulting in a temporary denial of service.
There are two factors influencing this relative immunity to DoS.
1. Entity list size: old game default is 1024 entities. Loxo uses 2048 by default.
2. List management code in LOX was modified long ago and is now an historic artifact in LOX.
Three functions were modified in g_utils.c to speed up entity initialization and freeing.
The functions are listed below. These functions are common to all Quake2 mods.
NOTE: The entity member "classnum" is used in LOX to speed up class comparisons instead of using string matching based on "classname". The CN_* values are simply #defines or enumated constants for all entity classes in the relevant mod. You may freely discard the code dealing with this data member.
In server configuration files you can set maxentities 2048 or you can modify g_save.c in the game source:
There are two factors influencing this relative immunity to DoS.
1. Entity list size: old game default is 1024 entities. Loxo uses 2048 by default.
2. List management code in LOX was modified long ago and is now an historic artifact in LOX.
Three functions were modified in g_utils.c to speed up entity initialization and freeing.
The functions are listed below. These functions are common to all Quake2 mods.
NOTE: The entity member "classnum" is used in LOX to speed up class comparisons instead of using string matching based on "classname". The CN_* values are simply #defines or enumated constants for all entity classes in the relevant mod. You may freely discard the code dealing with this data member.
Code: [Select]
void G_InitEdict (edict_t *e)
{
e->inuse = QTRUE;
e->classname = "noclass";
e->classnum = CN_NOCLASS;
e->gravity = 1.0;
e->s.number = e - g_edicts;
// Clear what the free-edict list may have set.
e->chain = NULL;
// This is another headache.
e->think = NULL;
e->nextthink = 0;
}
// The free-edict list. Meant to vastly speed up G_Spawn().
edict_t *g_freeEdictsH = NULL;
edict_t *g_freeEdictsT = NULL;
/*
=================
G_Spawn
Either finds a free edict, or allocates a new one.
Try to avoid reusing an entity that was recently freed, because it
can cause the client to think the entity morphed into something else
instead of being removed and recreated, which can cause interpolated
angles and bad trails.
=================
*/
edict_t *G_Spawn (void)
{
int i;
edict_t *e;
// char string[64];
// If the free-edict queue can help, let it.
while (g_freeEdictsH != NULL)
{
// Remove the first item.
e = g_freeEdictsH;
g_freeEdictsH = g_freeEdictsH->chain;
if (g_freeEdictsH == NULL)
g_freeEdictsT = NULL;
// If it's in use, get another one.
if (e->inuse)
continue;
// If it's safe to use it, do so.
if (e->freetime < 2 || level.time - e->freetime > 0.5)
{
G_InitEdict (e);
return e;
}
// If we can't use it, we won't be able to use any of these -- anything
// after it in the queue was freed even later.
else
break;
}
// The old way to find a free edict.
e = &g_edicts[(int)maxclients->value+1];
for ( i = (int) maxclients->value + 1 ; i < globals.num_edicts ; i++, e++)
{
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
{
G_InitEdict (e);
return e;
}
}
if (i == game.maxentities)
gi.error ("ED_Alloc: no free edicts");
globals.num_edicts++;
G_InitEdict (e);
// sprintf(string, "num_edicts is %i\n", globals.num_edicts);
// OutputDebugString(string);
return e;
}
/*
=================
G_FreeEdict
Marks the edict as free
=================
*/
void G_FreeEdict (edict_t *ed)
{
gi.unlinkentity (ed); // unlink from world
if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
{
// gi.dprintf("tried to free special edict\n");
return;
}
memset (ed, 0, sizeof(*ed));
ed->classname = "freed";
ed->classnum = CN_NOCLASS;
ed->freetime = level.time;
ed->inuse = QFALSE;
// Put this edict into the free-edict queue.
if (g_freeEdictsH == NULL)
g_freeEdictsH = ed;
else
g_freeEdictsT->chain = ed;
g_freeEdictsT = ed;
ed->chain = NULL;
}
In server configuration files you can set maxentities 2048 or you can modify g_save.c in the game source:
Code: [Select]
maxentities = gi.cvar ("maxentities", "2048", CVAR_LATCH);