From 0d5fef7f5c33fd4176b27c79d9cef7512801f471 Mon Sep 17 00:00:00 2001 From: Thilo Schulz Date: Wed, 27 Apr 2011 16:03:35 +0000 Subject: - Harden the client and server protocol against UDP spoofing attacks. This will defend ioquake3 against http://aluigi.altervista.org/papers/q3noclient.txt (#3041) - Retains full compatibility to the old but unsecure protocol between clients and servers - Harden the connection process against DoS attacks, possibly connected to UDP spoofing --- src/client/cl_main.c | 47 +++++++++++++++++++++++++---------------------- src/client/cl_net_chan.c | 6 ++---- src/qcommon/net_chan.c | 25 +++++++++++++++++++------ src/qcommon/q_shared.c | 4 +++- src/qcommon/q_shared.h | 2 +- src/qcommon/qcommon.h | 9 ++++++--- src/server/server.h | 8 ++++++-- src/server/sv_client.c | 47 +++++++++++++++++++++++++++++++++-------------- 8 files changed, 95 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/client/cl_main.c b/src/client/cl_main.c index 10bc313f..128db604 100644 --- a/src/client/cl_main.c +++ b/src/client/cl_main.c @@ -585,7 +585,6 @@ void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { len = clc.serverMessageSequence; swlen = LittleLong( len ); FS_Write (&swlen, 4, clc.demofile); - // skip the packet sequencing information len = msg->cursize - headerBytes; swlen = LittleLong(len); @@ -717,7 +716,6 @@ void CL_Record_f( void ) { clc.spDemoRecording = qfalse; } - Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); // don't start saving messages until a non-delta compressed message is received @@ -941,14 +939,13 @@ void CL_ReadDemoMessage( void ) { CL_WalkDemoExt ==================== */ -static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +static int CL_WalkDemoExt(char *arg, char *name, int *demofile) { int i = 0; *demofile = 0; - Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION); - - FS_FOpenFileRead( name, demofile, qtrue ); + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION); + FS_FOpenFileRead(name, demofile, qtrue); if (*demofile) { @@ -960,17 +957,23 @@ static void CL_WalkDemoExt(char *arg, char *name, int *demofile) while(demo_protocols[i]) { + if(demo_protocols[i] == PROTOCOL_VERSION) + continue; + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); FS_FOpenFileRead( name, demofile, qtrue ); if (*demofile) { Com_Printf("Demo file: %s\n", name); - break; + + return demo_protocols[i]; } else Com_Printf("Not found: %s\n", name); i++; } + + return -1; } /* @@ -1047,11 +1050,11 @@ void CL_PlayDemo_f( void ) { Q_strncpyz(retry, arg, len + 1); retry[len] = '\0'; - CL_WalkDemoExt(retry, name, &clc.demofile); + protocol = CL_WalkDemoExt(retry, name, &clc.demofile); } } else - CL_WalkDemoExt(arg, name, &clc.demofile); + protocol = CL_WalkDemoExt(arg, name, &clc.demofile); if (!clc.demofile) { Com_Error( ERR_DROP, "couldn't open %s", name); @@ -2603,6 +2606,7 @@ Responses to broadcasts, etc void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { char *s; char *c; + int challenge; MSG_BeginReadingOOB( msg ); MSG_ReadLong( msg ); // skip the -1 @@ -2624,15 +2628,17 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { return; } + c = Cmd_Argv(2); + if(*c) + challenge = atoi(c); + if(!NET_CompareAdr(from, clc.serverAddress)) { // This challenge response is not coming from the expected address. // Check whether we have a matching client challenge to prevent // connection hi-jacking. - - c = Cmd_Argv(2); - - if(!*c || atoi(c) != clc.challenge) + + if(!*c || challenge != clc.challenge) { Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); return; @@ -2666,7 +2672,10 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { Com_Printf( "connectResponse from wrong address. Ignored.\n" ); return; } - Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge); + cls.state = CA_CONNECTED; clc.lastPacketSentTime = -9999; // send first packet immediately return; @@ -2684,13 +2693,6 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { return; } - // a disconnect message from the server, which will happen if the server - // dropped the connection but it is still getting packets from us - if (!Q_stricmp(c, "disconnect")) { - CL_DisconnectPacket( from ); - return; - } - // echo request from server if ( !Q_stricmp(c, "echo") ) { NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); @@ -2704,7 +2706,7 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { } // echo request from server - if ( !Q_stricmp(c, "print") ) { + if(!Q_stricmp(c, "print")){ s = MSG_ReadString( msg ); Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); @@ -2719,6 +2721,7 @@ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { // list of servers sent back by a master server (classic) if ( !Q_strncmp(c, "getserversResponse", 18) ) { CL_ServersResponsePacket( &from, msg, qfalse ); + return; } diff --git a/src/client/cl_net_chan.c b/src/client/cl_net_chan.c index 561f322a..fbfc4dc9 100644 --- a/src/client/cl_net_chan.c +++ b/src/client/cl_net_chan.c @@ -148,9 +148,6 @@ void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { Netchan_Transmit( chan, msg->cursize, msg->data ); } -extern int oldsize; -int newsize = 0; - /* ================= CL_Netchan_Process @@ -162,7 +159,8 @@ qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { ret = Netchan_Process( chan, msg ); if (!ret) return qfalse; + CL_Netchan_Decode( msg ); - newsize += msg->cursize; + return qtrue; } diff --git a/src/qcommon/net_chan.c b/src/qcommon/net_chan.c index 4aacfa7e..67ef54e0 100644 --- a/src/qcommon/net_chan.c +++ b/src/qcommon/net_chan.c @@ -84,7 +84,8 @@ Netchan_Setup called to open a channel to a remote system ============== */ -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge) +{ Com_Memset (chan, 0, sizeof(*chan)); chan->sock = sock; @@ -191,17 +192,21 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) { msg_t send; byte send_buf[MAX_PACKETLEN]; int fragmentLength; + int outgoingSequence; // write the packet header MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here - MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + outgoingSequence = chan->outgoingSequence | FRAGMENT_BIT; + MSG_WriteLong(&send, outgoingSequence); // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + // copy the reliable message to the packet first fragmentLength = FRAGMENT_SIZE; if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { @@ -269,12 +274,14 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { MSG_InitOOB (&send, send_buf, sizeof(send_buf)); MSG_WriteLong( &send, chan->outgoingSequence ); - chan->outgoingSequence++; // send the qport if we are a client - if ( chan->sock == NS_CLIENT ) { - MSG_WriteShort( &send, qport->integer ); - } + if(chan->sock == NS_CLIENT) + MSG_WriteShort(&send, qport->integer); + + MSG_WriteLong(&send, NETCHAN_GENCHECKSUM(chan->challenge, chan->outgoingSequence)); + + chan->outgoingSequence++; MSG_WriteData( &send, data, length ); @@ -328,6 +335,12 @@ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { qport = MSG_ReadShort( msg ); } + int checksum = MSG_ReadLong(msg); + + // UDP spoofing protection + if(NETCHAN_GENCHECKSUM(chan->challenge, sequence) != checksum) + return qfalse; + // read the fragment information if ( fragmented ) { fragmentStart = MSG_ReadShort( msg ); diff --git a/src/qcommon/q_shared.c b/src/qcommon/q_shared.c index b4846799..9f7600b6 100644 --- a/src/qcommon/q_shared.c +++ b/src/qcommon/q_shared.c @@ -981,7 +981,7 @@ void Q_ParseNewlines( char *dest, const char *src, int destsize ) *dest++ = '\0'; } -void QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) +int QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) { int len; va_list argptr; @@ -992,6 +992,8 @@ void QDECL Com_sprintf(char *dest, int size, const char *fmt, ...) if(len >= size) Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len); + + return len; } /* diff --git a/src/qcommon/q_shared.h b/src/qcommon/q_shared.h index 1cedf6a8..b4ddc35e 100644 --- a/src/qcommon/q_shared.h +++ b/src/qcommon/q_shared.h @@ -741,7 +741,7 @@ void Parse2DMatrix (char **buf_p, int y, int x, float *m); void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m); int Com_HexStrToInt( const char *str ); -void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); +int QDECL Com_sprintf (char *dest, int size, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); char *Com_SkipTokens( char *s, int numTokens, char *sep ); char *Com_SkipCharset( char *s, char *sep ); diff --git a/src/qcommon/qcommon.h b/src/qcommon/qcommon.h index d09ee299..affde995 100644 --- a/src/qcommon/qcommon.h +++ b/src/qcommon/qcommon.h @@ -192,7 +192,8 @@ void NET_Sleep(int msec); #define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames #define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks - + +#define NETCHAN_GENCHECKSUM(challenge, sequence) ((challenge) ^ ((sequence) * (challenge))) /* Netchan handles packet fragmentation and out of order / duplicate suppression @@ -221,10 +222,12 @@ typedef struct { int unsentFragmentStart; int unsentLength; byte unsentBuffer[MAX_MSGLEN]; + + int challenge; } netchan_t; void Netchan_Init( int qport ); -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); +void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int challenge); void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); void Netchan_TransmitNextFragment( netchan_t *chan ); @@ -240,7 +243,7 @@ PROTOCOL ============================================================== */ -#define PROTOCOL_VERSION 70 +#define PROTOCOL_VERSION 71 // maintain a list of compatible protocols for demo playing // NOTE: that stuff only works with two digits protocols diff --git a/src/server/server.h b/src/server/server.h index 0475f0b9..697d8a30 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -196,7 +196,7 @@ typedef struct client_s { #endif int oldServerTime; - qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + qboolean csUpdated[MAX_CONFIGSTRINGS+1]; } client_t; //============================================================================= @@ -205,7 +205,11 @@ typedef struct client_s { // MAX_CHALLENGES is made large to prevent a denial // of service attack that could cycle all of them // out before legitimate users connected -#define MAX_CHALLENGES 1024 +#define MAX_CHALLENGES 2048 +// Allow a certain amount of challenges to have the same IP address +// to make it a bit harder to DOS one single IP address from connecting +// while not allowing a single ip to grab all challenge resources +#define MAX_CHALLENGES_MULTI (MAX_CHALLENGES / 2) #define AUTHORIZE_TIMEOUT 5000 diff --git a/src/server/sv_client.c b/src/server/sv_client.c index a79fc2bc..f69155c6 100644 --- a/src/server/sv_client.c +++ b/src/server/sv_client.c @@ -56,19 +56,36 @@ void SV_GetChallenge(netadr_t from) int i; int oldest; int oldestTime; - const char *clientChallenge = Cmd_Argv(1); + int oldestClientTime; + int clientChallenge; challenge_t *challenge; + qboolean wasfound = qfalse; oldest = 0; - oldestTime = 0x7fffffff; + oldestClientTime = oldestTime = 0x7fffffff; // see if we already have a challenge for this ip challenge = &svs.challenges[0]; - for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { - if (!challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { + clientChallenge = atoi(Cmd_Argv(1)); + + for(i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) + { + if(!challenge->connected && NET_CompareAdr(from, challenge->adr)) + { + wasfound = qtrue; + + if(challenge->time < oldestClientTime) + oldestClientTime = challenge->time; + } + + if(wasfound && i >= MAX_CHALLENGES_MULTI) + { + i = MAX_CHALLENGES; break; } - if ( challenge->time < oldestTime ) { + + if(challenge->time < oldestTime) + { oldestTime = challenge->time; oldest = i; } @@ -78,10 +95,9 @@ void SV_GetChallenge(netadr_t from) { // this is the first time this client has asked for a challenge challenge = &svs.challenges[oldest]; - challenge->clientChallenge = 0; + challenge->clientChallenge = clientChallenge; challenge->adr = from; challenge->firstTime = svs.time; - challenge->time = svs.time; challenge->connected = qfalse; } @@ -89,8 +105,9 @@ void SV_GetChallenge(netadr_t from) challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; challenge->wasrefused = qfalse; + challenge->time = svs.time; challenge->pingTime = svs.time; - NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %i %s", challenge->challenge, clientChallenge); + NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %i %d", challenge->challenge, clientChallenge); } /* @@ -120,10 +137,12 @@ void SV_DirectConnect( netadr_t from ) { Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); - version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); - if ( version != PROTOCOL_VERSION ) { - NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i\n", PROTOCOL_VERSION ); - Com_DPrintf (" rejected connect from version %i\n", version); + version = atoi(Info_ValueForKey(userinfo, "protocol")); + if(version != PROTOCOL_VERSION) + { + NET_OutOfBandPrint(NS_SERVER, from, "print\nServer uses protocol version %i " + "(yours is %i).\n", com_protocol->integer, version); + Com_DPrintf(" rejected connect from version %i\n", version); return; } @@ -291,7 +310,7 @@ gotnewcl: newcl->challenge = challenge; // save the address - Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); + Netchan_Setup(NS_SERVER, &newcl->netchan, from, qport, challenge); // init the netchan queue newcl->netchan_end_queue = &newcl->netchan_start_queue; @@ -312,7 +331,7 @@ gotnewcl: SV_UserinfoChanged( newcl ); // send the connect packet to the client - NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + NET_OutOfBandPrint(NS_SERVER, from, "connectResponse %d", challenge); Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); -- cgit