OK guys, I considered silently ignoring say/radio commands that are too long, but simply kicking the client is safer. There could be some side effects of dropping a command and not kicking the client. However, this is probably safe.
For your reference here is the code to determine when a say/radio command is too long. Taken from sv_client.c:
// The value below is how many extra characters we reserve for every instance of '$' in a
// ut_radio, say, or similar client command. Some jump maps have very long $location's.
// On these maps, it may be possible to crash the server if a carefully-crafted
// client command is sent. The constant below may require further tweaking. For example,
// a text of "$location" would have a total computed length of 25, because "$location" has
// 9 characters, and we increment that by 16 for the '$'.
#define STRLEN_INCREMENT_PER_DOLLAR_VAR 16
// Don't allow more than this many dollared-strings (e.g. $location) in a client command
// such as ut_radio and say. Keep this value low for safety, in case some things like
// $location expand to very large strings in some maps. There is really no reason to have
// more than 6 dollar vars (such as $weapon or $location) in things you tell other people.
#define MAX_DOLLAR_VARS 6
// When a radio text (as in "ut_radio 1 1 text") is sent, weird things start to happen
// when the text gets to be greater than 118 in length. When the text is really large the
// server will crash. There is an in-between gray zone above 118, but I don't really want
// to go there. This is the maximum length of radio text that can be sent, taking into
// account increments due to presence of '$'.
#define MAX_RADIO_STRLEN 118
// Don't allow more than this text length in a command such as say. I pulled this
// value out of my ass because I don't really know exactly when problems start to happen.
// This value takes into account increments due to the presence of '$'.
#define MAX_SAY_STRLEN 256
void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) {
ucmd_t *u;
qboolean bProcessed = qfalse;
int argsFromOneMaxlen;
int charCount;
int dollarCount;
int i;
char *arg;
qboolean exploitDetected;
Cmd_TokenizeString( s );
// see if it is a server level command
for (u=ucmds ; u->name ; u++) {
if (!strcmp (Cmd_Argv(0), u->name) ) {
u->func( cl );
bProcessed = qtrue;
break;
}
}
if (clientOK) {
// pass unknown strings to the game
if (!u->name && sv.state == SS_GAME) {
Cmd_Args_Sanitize();
argsFromOneMaxlen = -1;
if (Q_stricmp("say", Cmd_Argv(0)) == 0 ||
Q_stricmp("say_team", Cmd_Argv(0)) == 0) {
argsFromOneMaxlen = MAX_SAY_STRLEN;
}
else if (Q_stricmp("tell", Cmd_Argv(0)) == 0) {
// A command will look like "tell 12 hi" or "tell foo hi". The "12"
// and "foo" in the examples will be counted towards MAX_SAY_STRLEN,
// plus the space.
argsFromOneMaxlen = MAX_SAY_STRLEN;
}
else if (Q_stricmp("ut_radio", Cmd_Argv(0)) == 0) {
// We add 4 to this value because in a command such as
// "ut_radio 1 1 affirmative", the args at indices 1 and 2 each
// have length 1 and there is a space after them.
argsFromOneMaxlen = MAX_RADIO_STRLEN + 4;
}
if (argsFromOneMaxlen >= 0) {
exploitDetected = qfalse;
charCount = 0;
dollarCount = 0;
for (i = Cmd_Argc() - 1; i >= 1; i--) {
arg = Cmd_Argv(i);
while (*arg) {
if (++charCount > argsFromOneMaxlen) {
exploitDetected = qtrue; break;
}
if (*arg == '$') {
if (++dollarCount > MAX_DOLLAR_VARS) {
exploitDetected = qtrue; break;
}
charCount += STRLEN_INCREMENT_PER_DOLLAR_VAR;
if (charCount > argsFromOneMaxlen) {
exploitDetected = qtrue; break;
}
}
arg++;
}
if (exploitDetected) { break; }
if (i != 1) { // Cmd_ArgsFrom() will add space
if (++charCount > argsFromOneMaxlen) {
exploitDetected = qtrue; break;
}
}
}
if (exploitDetected) {
Com_Printf("Buffer overflow exploit radio/say, possible attempt from %s\n",
NET_AdrToString(cl->netchan.remoteAddress));
SV_DropClient(cl, "talks too much");
return;
}
}
//////////////////////////////////////////////////////////
// separator for logcallvote.patch and forceautojoin.patch
//////////////////////////////////////////////////////////
VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients );
}
}
else if (!bProcessed)
Com_DPrintf( "client text ignored for %s: %s\n", cl->name, Cmd_Argv(0) );
}