[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#666222: pu: package tremulous/1.1.0-8~squeeze1 (contrib)



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: pu

This update reduces attackers' ability to perform a reflected DoS attack by
sending spoofed UDP packets to multiple Tremulous servers, by rate-limiting
large responses to those packets. It's the same thing as DSA-2442-1 in
OpenArena, but also incorporates a fix for a regression in that update
(I've just uploaded the corresponding fix for OpenArena to security-master).

As with -7~squeeze1, the security team have instructed me to go directly
to stable since Tremulous is contrib, and the source I propose to upload is
identical to what's in unstable (apart from the changelog).

The reporter of the corresponding OpenArena bug (#665665) is testing
pre-release versions of the Tremulous and OA updates on a public server
running squeeze, and hasn't reported any problems so far.

Changelog below; debdiff attached; separated patch also attached, for better
legibility.

Regards,
    S

tremulous (1.1.0-8~squeeze1) stable; urgency=low

  * Stable update, incorporating a security fix from unstable

 -- Simon McVittie <smcv@debian.org>  Thu, 29 Mar 2012 20:40:49 +0100

tremulous (1.1.0-8) unstable; urgency=medium

  * Backport ioquake3 r1762, r1763, r1898 to rate-limit getstatus and
    rcon connectionless packets, to avoid their use for traffic amplification.
    CVE-2010-5077 (Closes: #665842)
  * Fix an incorrect bug number in revision -6

 -- Simon McVittie <smcv@debian.org>  Tue, 27 Mar 2012 20:33:10 +0100
diffstat for tremulous-1.1.0 tremulous-1.1.0

 changelog                                                               |   15 
 patches/0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch |  263 ++++++++++
 patches/series                                                          |    1 
 3 files changed, 279 insertions(+)

diff -Nru tremulous-1.1.0/debian/changelog tremulous-1.1.0/debian/changelog
--- tremulous-1.1.0/debian/changelog	2012-03-25 13:57:34.000000000 +0100
+++ tremulous-1.1.0/debian/changelog	2012-03-29 20:40:50.000000000 +0100
@@ -1,3 +1,18 @@
+tremulous (1.1.0-8~squeeze1) stable; urgency=low
+
+  * Stable update, incorporating a security fix from unstable
+
+ -- Simon McVittie <smcv@debian.org>  Thu, 29 Mar 2012 20:40:49 +0100
+
+tremulous (1.1.0-8) unstable; urgency=medium
+
+  * Backport ioquake3 r1762, r1763, r1898 to rate-limit getstatus and
+    rcon connectionless packets, to avoid their use for traffic amplification.
+    CVE-2010-5077 (Closes: #665842)
+  * Fix an incorrect bug number in revision -6
+
+ -- Simon McVittie <smcv@debian.org>  Tue, 27 Mar 2012 20:33:10 +0100
+
 tremulous (1.1.0-7~squeeze1) stable; urgency=low
 
   * Stable update (#663104), incorporating security fixes from unstable
diff -Nru tremulous-1.1.0/debian/patches/0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch tremulous-1.1.0/debian/patches/0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch
--- tremulous-1.1.0/debian/patches/0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch	1970-01-01 01:00:00.000000000 +0100
+++ tremulous-1.1.0/debian/patches/0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch	2012-03-27 22:05:46.000000000 +0100
@@ -0,0 +1,263 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 3 Jan 2010 22:12:20 +0000
+Subject: Rate limit getstatus and rcon connectionless requests
+
+Backport of ioquake3 r1762, r1763, r1898, all by Tim Angus <tma>. This
+also incorporates a fix for a regression in r1762 in which the server would
+stop responding to getstatus after 2**32 ms (about 50 days).
+
+Changes to adapt to Tremulous:
+
+* Remove IPv6 support, Tremulous 1.1.0 does not do IPv6
+* Do not assume that NA_BAD == 0 (in this older version it's 1),
+  look for literal 0 as the indication that a hash bucket has only been
+  zero-filled and not properly initialized
+* Remove cosmetic (whitespace/comment) changes
+
+Origin: backport
+Bug-Debian: http://bugs.debian.org/665842
+CVE: CVE-2010-5077
+---
+ src/server/sv_main.c |  210 +++++++++++++++++++++++++++++++++++++++++++++++---
+ 1 files changed, 200 insertions(+), 10 deletions(-)
+
+diff --git a/src/server/sv_main.c b/src/server/sv_main.c
+index f6d5a7c..9dfa6dc 100644
+--- a/src/server/sv_main.c
++++ b/src/server/sv_main.c
+@@ -332,6 +332,175 @@ CONNECTIONLESS COMMANDS
+ ==============================================================================
+ */
+ 
++typedef struct leakyBucket_s leakyBucket_t;
++struct leakyBucket_s {
++	netadrtype_t	type;
++
++	union {
++		byte	_4[4];
++		byte	_6[16];
++	} ipv;
++
++	int						lastTime;
++	signed char		burst;
++
++	long					hash;
++
++	leakyBucket_t *prev, *next;
++};
++
++// This is deliberately quite large to make it more of an effort to DoS
++#define MAX_BUCKETS			16384
++#define MAX_HASHES			1024
++
++static leakyBucket_t buckets[ MAX_BUCKETS ];
++static leakyBucket_t *bucketHashes[ MAX_HASHES ];
++
++/*
++================
++SVC_HashForAddress
++================
++*/
++static long SVC_HashForAddress( netadr_t address ) {
++	byte 		*ip = NULL;
++	size_t	size = 0;
++	int			i;
++	long		hash = 0;
++
++	switch ( address.type ) {
++		case NA_IP:  ip = address.ip;  size = 4; break;
++		default: return 0;
++	}
++
++	for ( i = 0; i < size; i++ ) {
++		hash += (long)( ip[ i ] ) * ( i + 119 );
++	}
++
++	hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) );
++	hash &= ( MAX_HASHES - 1 );
++
++	return hash;
++}
++
++/*
++================
++SVC_BucketForAddress
++
++Find or allocate a bucket for an address
++================
++*/
++static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) {
++	leakyBucket_t	*bucket = NULL;
++	int						i;
++	long					hash = SVC_HashForAddress( address );
++	int						now = Sys_Milliseconds();
++
++	for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) {
++		switch ( bucket->type ) {
++			case NA_IP:
++				if ( memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) {
++					return bucket;
++				}
++				break;
++
++			default:
++				break;
++		}
++	}
++
++	for ( i = 0; i < MAX_BUCKETS; i++ ) {
++		int interval;
++
++		bucket = &buckets[ i ];
++		interval = now - bucket->lastTime;
++
++		// Reclaim expired buckets
++		if ( bucket->lastTime > 0 && ( interval > ( burst * period ) ||
++					interval < 0 ) ) {
++			if ( bucket->prev != NULL ) {
++				bucket->prev->next = bucket->next;
++			} else {
++				bucketHashes[ bucket->hash ] = bucket->next;
++			}
++			
++			if ( bucket->next != NULL ) {
++				bucket->next->prev = bucket->prev;
++			}
++
++			Com_Memset( bucket, 0, sizeof( leakyBucket_t ) );
++		}
++
++		if ( bucket->type == 0 ) {
++			bucket->type = address.type;
++			switch ( address.type ) {
++				case NA_IP:  Com_Memcpy( bucket->ipv._4, address.ip, 4 );   break;
++				default: break;
++			}
++
++			bucket->lastTime = now;
++			bucket->burst = 0;
++			bucket->hash = hash;
++
++			// Add to the head of the relevant hash chain
++			bucket->next = bucketHashes[ hash ];
++			if ( bucketHashes[ hash ] != NULL ) {
++				bucketHashes[ hash ]->prev = bucket;
++			}
++
++			bucket->prev = NULL;
++			bucketHashes[ hash ] = bucket;
++
++			return bucket;
++		}
++	}
++
++	// Couldn't allocate a bucket for this address
++	return NULL;
++}
++
++/*
++================
++SVC_RateLimit
++================
++*/
++static qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) {
++	if ( bucket != NULL ) {
++		int now = Sys_Milliseconds();
++		int interval = now - bucket->lastTime;
++		int expired = interval / period;
++		int expiredRemainder = interval % period;
++
++		if ( expired > bucket->burst ) {
++			bucket->burst = 0;
++			bucket->lastTime = now;
++		} else {
++			bucket->burst -= expired;
++			bucket->lastTime = now - expiredRemainder;
++		}
++
++		if ( bucket->burst < burst ) {
++			bucket->burst++;
++
++			return qfalse;
++		}
++	}
++
++	return qtrue;
++}
++
++/*
++================
++SVC_RateLimitAddress
++
++Rate limit for a particular address
++================
++*/
++static qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ) {
++	leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period );
++
++	return SVC_RateLimit( bucket, burst, period );
++}
++
+ /*
+ ================
+ SVC_Status
+@@ -350,6 +519,21 @@ void SVC_Status( netadr_t from ) {
+ 	int		statusLength;
+ 	int		playerLength;
+ 	char	infostring[MAX_INFO_STRING];
++	static leakyBucket_t bucket;
++
++	// Prevent using getstatus as an amplifier
++	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
++		Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
++			NET_AdrToString( from ) );
++		return;
++	}
++
++	// Allow getstatus to be DoSed relatively easily, but prevent
++	// excess outbound bandwidth usage when being flooded inbound
++	if ( SVC_RateLimit( &bucket, 10, 100 ) ) {
++		Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
++		return;
++	}
+ 
+ 	strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
+ 
+@@ -466,24 +650,30 @@ Redirect all printfs
+ */
+ void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
+ 	qboolean	valid;
+-	unsigned int time;
+ 	char		remaining[1024];
+ 	// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
+ 	// (OOB messages are the bottleneck here)
+ #define SV_OUTPUTBUF_LENGTH (1024 - 16)
+ 	char		sv_outputbuf[SV_OUTPUTBUF_LENGTH];
+-	static unsigned int lasttime = 0;
+ 	char *cmd_aux;
+ 
+-	// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
+-	time = Com_Milliseconds();
+-	if (time<(lasttime+500)) {
++	// Prevent using rcon as an amplifier and make dictionary attacks impractical
++	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
++		Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
++			NET_AdrToString( from ) );
+ 		return;
+ 	}
+-	lasttime = time;
+ 
+ 	if ( !strlen( sv_rconPassword->string ) ||
+ 		strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
++		static leakyBucket_t bucket;
++
++		// Make DoS via rcon impractical
++		if ( SVC_RateLimit( &bucket, 10, 1000 ) ) {
++			Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
++			return;
++		}
++
+ 		valid = qfalse;
+ 		Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
+ 	} else {
+
diff -Nru tremulous-1.1.0/debian/patches/series tremulous-1.1.0/debian/patches/series
--- tremulous-1.1.0/debian/patches/series	2012-02-22 09:09:26.000000000 +0000
+++ tremulous-1.1.0/debian/patches/series	2012-03-27 22:05:46.000000000 +0100
@@ -17,3 +17,4 @@
 0017-Sys_Error-do-not-overflow-if-an-error-message-exceed.patch
 0018-Avoid-non-literal-format-strings.patch
 0019-Annotate-printf-and-scanf-like-functions-with-gcc-at.patch
+0020-Rate-limit-getstatus-and-rcon-connectionless-request.patch
From: Simon McVittie <smcv@debian.org>
Date: Sun, 3 Jan 2010 22:12:20 +0000
Subject: Rate limit getstatus and rcon connectionless requests

Backport of ioquake3 r1762, r1763, r1898, all by Tim Angus <tma>. This
also incorporates a fix for a regression in r1762 in which the server would
stop responding to getstatus after 2**32 ms (about 50 days).

Changes to adapt to Tremulous:

* Remove IPv6 support, Tremulous 1.1.0 does not do IPv6
* Do not assume that NA_BAD == 0 (in this older version it's 1),
  look for literal 0 as the indication that a hash bucket has only been
  zero-filled and not properly initialized
* Remove cosmetic (whitespace/comment) changes

Origin: backport
Bug-Debian: http://bugs.debian.org/665842
CVE: CVE-2010-5077
---
 src/server/sv_main.c |  210 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 200 insertions(+), 10 deletions(-)

diff --git a/src/server/sv_main.c b/src/server/sv_main.c
index f6d5a7c..9dfa6dc 100644
--- a/src/server/sv_main.c
+++ b/src/server/sv_main.c
@@ -332,6 +332,175 @@ CONNECTIONLESS COMMANDS
 ==============================================================================
 */
 
+typedef struct leakyBucket_s leakyBucket_t;
+struct leakyBucket_s {
+	netadrtype_t	type;
+
+	union {
+		byte	_4[4];
+		byte	_6[16];
+	} ipv;
+
+	int						lastTime;
+	signed char		burst;
+
+	long					hash;
+
+	leakyBucket_t *prev, *next;
+};
+
+// This is deliberately quite large to make it more of an effort to DoS
+#define MAX_BUCKETS			16384
+#define MAX_HASHES			1024
+
+static leakyBucket_t buckets[ MAX_BUCKETS ];
+static leakyBucket_t *bucketHashes[ MAX_HASHES ];
+
+/*
+================
+SVC_HashForAddress
+================
+*/
+static long SVC_HashForAddress( netadr_t address ) {
+	byte 		*ip = NULL;
+	size_t	size = 0;
+	int			i;
+	long		hash = 0;
+
+	switch ( address.type ) {
+		case NA_IP:  ip = address.ip;  size = 4; break;
+		default: return 0;
+	}
+
+	for ( i = 0; i < size; i++ ) {
+		hash += (long)( ip[ i ] ) * ( i + 119 );
+	}
+
+	hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) );
+	hash &= ( MAX_HASHES - 1 );
+
+	return hash;
+}
+
+/*
+================
+SVC_BucketForAddress
+
+Find or allocate a bucket for an address
+================
+*/
+static leakyBucket_t *SVC_BucketForAddress( netadr_t address, int burst, int period ) {
+	leakyBucket_t	*bucket = NULL;
+	int						i;
+	long					hash = SVC_HashForAddress( address );
+	int						now = Sys_Milliseconds();
+
+	for ( bucket = bucketHashes[ hash ]; bucket; bucket = bucket->next ) {
+		switch ( bucket->type ) {
+			case NA_IP:
+				if ( memcmp( bucket->ipv._4, address.ip, 4 ) == 0 ) {
+					return bucket;
+				}
+				break;
+
+			default:
+				break;
+		}
+	}
+
+	for ( i = 0; i < MAX_BUCKETS; i++ ) {
+		int interval;
+
+		bucket = &buckets[ i ];
+		interval = now - bucket->lastTime;
+
+		// Reclaim expired buckets
+		if ( bucket->lastTime > 0 && ( interval > ( burst * period ) ||
+					interval < 0 ) ) {
+			if ( bucket->prev != NULL ) {
+				bucket->prev->next = bucket->next;
+			} else {
+				bucketHashes[ bucket->hash ] = bucket->next;
+			}
+			
+			if ( bucket->next != NULL ) {
+				bucket->next->prev = bucket->prev;
+			}
+
+			Com_Memset( bucket, 0, sizeof( leakyBucket_t ) );
+		}
+
+		if ( bucket->type == 0 ) {
+			bucket->type = address.type;
+			switch ( address.type ) {
+				case NA_IP:  Com_Memcpy( bucket->ipv._4, address.ip, 4 );   break;
+				default: break;
+			}
+
+			bucket->lastTime = now;
+			bucket->burst = 0;
+			bucket->hash = hash;
+
+			// Add to the head of the relevant hash chain
+			bucket->next = bucketHashes[ hash ];
+			if ( bucketHashes[ hash ] != NULL ) {
+				bucketHashes[ hash ]->prev = bucket;
+			}
+
+			bucket->prev = NULL;
+			bucketHashes[ hash ] = bucket;
+
+			return bucket;
+		}
+	}
+
+	// Couldn't allocate a bucket for this address
+	return NULL;
+}
+
+/*
+================
+SVC_RateLimit
+================
+*/
+static qboolean SVC_RateLimit( leakyBucket_t *bucket, int burst, int period ) {
+	if ( bucket != NULL ) {
+		int now = Sys_Milliseconds();
+		int interval = now - bucket->lastTime;
+		int expired = interval / period;
+		int expiredRemainder = interval % period;
+
+		if ( expired > bucket->burst ) {
+			bucket->burst = 0;
+			bucket->lastTime = now;
+		} else {
+			bucket->burst -= expired;
+			bucket->lastTime = now - expiredRemainder;
+		}
+
+		if ( bucket->burst < burst ) {
+			bucket->burst++;
+
+			return qfalse;
+		}
+	}
+
+	return qtrue;
+}
+
+/*
+================
+SVC_RateLimitAddress
+
+Rate limit for a particular address
+================
+*/
+static qboolean SVC_RateLimitAddress( netadr_t from, int burst, int period ) {
+	leakyBucket_t *bucket = SVC_BucketForAddress( from, burst, period );
+
+	return SVC_RateLimit( bucket, burst, period );
+}
+
 /*
 ================
 SVC_Status
@@ -350,6 +519,21 @@ void SVC_Status( netadr_t from ) {
 	int		statusLength;
 	int		playerLength;
 	char	infostring[MAX_INFO_STRING];
+	static leakyBucket_t bucket;
+
+	// Prevent using getstatus as an amplifier
+	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
+		Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
+			NET_AdrToString( from ) );
+		return;
+	}
+
+	// Allow getstatus to be DoSed relatively easily, but prevent
+	// excess outbound bandwidth usage when being flooded inbound
+	if ( SVC_RateLimit( &bucket, 10, 100 ) ) {
+		Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
+		return;
+	}
 
 	strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) );
 
@@ -466,24 +650,30 @@ Redirect all printfs
 */
 void SVC_RemoteCommand( netadr_t from, msg_t *msg ) {
 	qboolean	valid;
-	unsigned int time;
 	char		remaining[1024];
 	// TTimo - scaled down to accumulate, but not overflow anything network wise, print wise etc.
 	// (OOB messages are the bottleneck here)
 #define SV_OUTPUTBUF_LENGTH (1024 - 16)
 	char		sv_outputbuf[SV_OUTPUTBUF_LENGTH];
-	static unsigned int lasttime = 0;
 	char *cmd_aux;
 
-	// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534
-	time = Com_Milliseconds();
-	if (time<(lasttime+500)) {
+	// Prevent using rcon as an amplifier and make dictionary attacks impractical
+	if ( SVC_RateLimitAddress( from, 10, 1000 ) ) {
+		Com_DPrintf( "SVC_Status: rate limit from %s exceeded, dropping request\n",
+			NET_AdrToString( from ) );
 		return;
 	}
-	lasttime = time;
 
 	if ( !strlen( sv_rconPassword->string ) ||
 		strcmp (Cmd_Argv(1), sv_rconPassword->string) ) {
+		static leakyBucket_t bucket;
+
+		// Make DoS via rcon impractical
+		if ( SVC_RateLimit( &bucket, 10, 1000 ) ) {
+			Com_DPrintf( "SVC_Status: rate limit exceeded, dropping request\n" );
+			return;
+		}
+
 		valid = qfalse;
 		Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) );
 	} else {


Reply to: