engine: client: introduce cookie protocol extension, when client connects to the server, it provides randomized 64-bit value to be prefixed into netchan messages

* Prevent clientuseragent from leaking data.
* Process qport in netchan, cleaning up code
This commit is contained in:
Alibek Omarov
2026-05-25 23:29:04 +05:00
committed by a1batross
parent d61983a5d2
commit e5c4e49782
7 changed files with 152 additions and 30 deletions

View File

@@ -259,10 +259,24 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, voi
chan->use_bz2 = FBitSet( flags, NETCHAN_USE_BZIP2 ) ? true : false;
chan->use_lzss = FBitSet( flags, NETCHAN_USE_LZSS ) ? true : false;
chan->gs_netchan = FBitSet( flags, NETCHAN_GOLDSRC ) ? true : false;
chan->use_cookie = FBitSet( flags, NETCHAN_USE_COOKIE ) ? true : false;
chan->cookie = 0;
MSG_Init( &chan->message, "NetData", chan->message_buf, sizeof( chan->message_buf ));
}
/*
==============
Netchan_SetCookie
called on the client after parsing NET_EXT_NETCHAN_COOKIE in the connect reply
==============
*/
void Netchan_SetCookie( netchan_t *chan, uint64_t cookie )
{
chan->cookie = cookie;
}
/*
==============================
Netchan_IncomingReady
@@ -1673,6 +1687,14 @@ void Netchan_TransmitBits( netchan_t *chan, int length, const byte *data )
chan->outgoing_sequence++;
// prefix the cookie so the peer can authenticate this packet as ours
// before doing anything else with it
if( chan->use_cookie )
{
MSG_WriteLong( &send, (uint)( chan->cookie & 0xFFFFFFFF ));
MSG_WriteLong( &send, (uint)( chan->cookie >> 32 ));
}
MSG_WriteLong( &send, w1 );
MSG_WriteLong( &send, w2 );
@@ -1797,15 +1819,42 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )
// get sequence numbers
MSG_Clear( msg );
// authenticate via the per-connection cookie before parsing anything else;
// a spoofed packet from a remote attacker won't know the 64-bit cookie and
// will be rejected here without touching sequence/ack state
if( chan->use_cookie )
{
if( MSG_GetMaxBytes( msg ) < 16 )
{
Con_Reportf( S_WARN "%s: %s: truncated packet (%d bytes) with cookie expected, dropping\n", __func__, NET_AdrToString( chan->remote_address ), MSG_GetMaxBytes( msg ));
return false;
}
uint32_t cookie_lo = MSG_ReadDword( msg );
uint32_t cookie_hi = MSG_ReadDword( msg );
uint64_t cookie = ((uint64_t)cookie_hi << 32 ) | (uint64_t)cookie_lo;
if( cookie != chan->cookie )
{
Con_Reportf( S_WARN "%s: %s: cookie mismatch, dropping (possible spoof attempt)\n", __func__, NET_AdrToString( chan->remote_address ));
return false;
}
}
uint sequence = MSG_ReadLong( msg );
uint sequence_ack = MSG_ReadLong( msg );
if( chan->use_munge && MSG_GetMaxBytes( msg ) >= 8 )
COM_UnMunge2( msg->pData + 8, MSG_GetMaxBytes( msg ) - 8, sequence & 0xFF );
// read the qport if we are a server
// read the qport if we are a server; serves as a NAT-stable
// connection demultiplexer and rejects packets for the wrong client
if( chan->sock == NS_SERVER )
MSG_ReadShort( msg );
{
if(( MSG_ReadShort( msg ) & 0xffff ) != chan->qport )
return false;
}
uint reliable_message = sequence >> 31;
uint reliable_ack = sequence_ack >> 31;
@@ -1870,7 +1919,9 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg )
// reject packets that leap too far ahead of the expected sequence
// skip on the very first packet — the server starts with a random
// outgoing_sequence, so the first one legitimately jumps far ahead of 0
if( chan->incoming_sequence != 0 && net_sequence_window.value > 0 && sequence > chan->incoming_sequence + (uint)net_sequence_window.value )
// NOTE: disable sequence window with cookie extension, if cookie ext proves
// to be inefficient, we can safely enable sequence window back
if( !chan->use_cookie && chan->incoming_sequence != 0 && net_sequence_window.value > 0 && sequence > chan->incoming_sequence + (uint)net_sequence_window.value )
{
Con_Printf( S_WARN "%s: %s: sequence %u jumps %u ahead of expected %i (window %i), dropping\n",
__func__, NET_AdrToString( chan->remote_address ),

View File

@@ -167,10 +167,11 @@ typedef enum fragsize_e
typedef enum netchan_flags_e
{
NETCHAN_USE_MUNGE = BIT( 0 ),
NETCHAN_USE_BZIP2 = BIT( 1 ),
NETCHAN_GOLDSRC = BIT( 2 ),
NETCHAN_USE_LZSS = BIT( 3 ), // mutually exclusive with bzip2
NETCHAN_USE_MUNGE = BIT( 0 ),
NETCHAN_USE_BZIP2 = BIT( 1 ),
NETCHAN_GOLDSRC = BIT( 2 ),
NETCHAN_USE_LZSS = BIT( 3 ), // mutually exclusive with bzip2
NETCHAN_USE_COOKIE = BIT( 4 ), // per-connection 64-bit cookie prefixed to every sequenced packet (NET_EXT_NETCHAN_COOKIE)
} netchan_flags_t;
// Network Connection Channel
@@ -240,6 +241,8 @@ typedef struct netchan_s
qboolean use_bz2;
qboolean use_lzss;
qboolean gs_netchan;
qboolean use_cookie;
uint64_t cookie;
} netchan_t;
extern netadr_t net_from;
@@ -252,6 +255,7 @@ extern int net_drop;
void Netchan_Init( void );
void Netchan_Shutdown( void );
void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags );
void Netchan_SetCookie( netchan_t *chan, uint64_t cookie );
void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size );
qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length );
qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg );

View File

@@ -283,7 +283,8 @@ extern const char *const svc_quake_strings[svc_lastmsg+1];
extern const char *const svc_goldsrc_strings[svc_lastmsg+1];
// FWGS extensions
#define NET_EXT_SPLITSIZE (1U<<0) // set splitsize by cl_dlmax
#define NET_EXT_SPLITSIZE (1U<<0) // set splitsize by cl_dlmax
#define NET_EXT_NETCHAN_COOKIE (1U<<1) // per-connection 64-bit netchan cookie validated on every sequenced packet
// GoldSrc protocol definitions
#define PROTOCOL_GOLDSRC_VERSION 48