From 44b9004d2c208b42c6f8ffa99938361e31f5a071 From: MadMaurice Date: Thu Aug 30 15:08:01 2018 +0200 Prevent instability and crash due to message flood This patch adds a rate limiting to selected patches. The underlying rate limiter used is the Leaky-Bucket algorithm. It allows for a burst of messages, but limits them after a specified amount of messages within a time frame. From: Ferdinand Thiessen Modified this diff, to make it work with 1.2.19 tarball. "Backported" by manually change the 1.2.19 version according to the original diff. diff -Nur mumble-1.2.19/src/murmur/Messages.cpp new/src/murmur/Messages.cpp --- mumble-1.2.19/src/murmur/Messages.cpp 2017-01-27 07:48:33.000000000 +0100 +++ new/src/murmur/Messages.cpp 2019-07-13 00:45:48.281780195 +0200 @@ -42,6 +42,11 @@ #include "ServerUser.h" #include "Version.h" +#define RATELIMIT(user) \ + if (user->leakyBucket.ratelimit(1)) { \ + return; \ + } + #define MSG_SETUP(st) \ if (uSource->sState != st) { \ return; \ @@ -491,6 +496,10 @@ msg.set_session(pDstServerUser->uiSession); msg.set_actor(uSource->uiSession); + if (uSource == pDstServerUser) { + RATELIMIT(uSource); + } + if (msg.has_channel_id()) { Channel *c = qhChannels.value(msg.channel_id()); if (!c || (c == pDstServerUser->cChannel)) @@ -798,6 +807,8 @@ p = qhChannels.value(msg.parent()); if (! p) return; + } else { + RATELIMIT(uSource); } msg.clear_links(); @@ -1074,6 +1085,8 @@ QSet users; QQueue q; + RATELIMIT(uSource); + QString text = u8(msg.message()); bool changed = false; @@ -1176,6 +1189,8 @@ return; } + RATELIMIT(uSource); + if (msg.has_query() && msg.query()) { QStack chans; Channel *p; @@ -1417,6 +1432,8 @@ } void Server::msgVersion(ServerUser *uSource, MumbleProto::Version &msg) { + RATELIMIT(uSource); + if (msg.has_version()) uSource->uiVersion=msg.version(); if (msg.has_release()) diff -Nur mumble-1.2.19/src/murmur/ServerUser.cpp new/src/murmur/ServerUser.cpp --- mumble-1.2.19/src/murmur/ServerUser.cpp 2017-01-27 07:48:33.000000000 +0100 +++ new/src/murmur/ServerUser.cpp 2019-07-13 00:47:25.974498227 +0200 @@ -128,3 +128,61 @@ return static_cast((sum * 1000000ULL) / elapsed); } +#if __cplusplus > 199711LL + +inline static +time_point now() { + return std::chrono::steady_clock::now(); +} + +inline static +unsigned long millisecondsBetween(time_point start, time_point end) { + return std::chrono::duration_cast(end - start).count(); +} + +#else + +inline static +time_point now() { + return clock(); +} + +inline static +unsigned long millisecondsBetween(time_point start, time_point end) { + return 1000 * (end - start) / CLOCKS_PER_SEC; +} + +#endif + +// Rate limiting: burst up to 30, 4 message per sec limit over longer time +LeakyBucket::LeakyBucket() : tokensPerSec(4), maxTokens(30), currentTokens(0) { + lastUpdate = now(); +} + +bool LeakyBucket::ratelimit(int tokens) { + // First remove tokens we leaked over time + time_point tnow = now(); + long ms = millisecondsBetween(lastUpdate, tnow); + + long drainTokens = (ms * tokensPerSec) / 1000; + + // Prevent constant starvation due to too many updates + if (drainTokens > 0) { + this->lastUpdate = tnow; + + this->currentTokens -= drainTokens; + if (this->currentTokens < 0) { + this->currentTokens = 0; + } + } + + // Then try to add tokens + bool limit = this->currentTokens > ((static_cast(maxTokens)) - tokens); + + // If the bucket is not overflowed, allow message and add tokens + if (!limit) { + this->currentTokens += tokens; + } + + return limit; +} diff -Nur mumble-1.2.19/src/murmur/ServerUser.h new/src/murmur/ServerUser.h --- mumble-1.2.19/src/murmur/ServerUser.h 2017-01-27 07:48:33.000000000 +0100 +++ new/src/murmur/ServerUser.h 2019-07-13 00:49:28.023395272 +0200 @@ -40,6 +40,13 @@ #include #endif +// was introduced in C++11 +#if __cplusplus > 199711LL +#include +#else +#include +#endif + #include "Connection.h" #include "Net.h" #include "Timer.h" @@ -80,6 +87,26 @@ class Server; +#if __cplusplus > 199711L + typedef std::chrono::time_point time_point; +#else + typedef clock_t time_point; +#endif + +// Simple algorithm for rate limiting +class LeakyBucket { + private: + unsigned int tokensPerSec, maxTokens; + long currentTokens; + time_point lastUpdate; + + public: + // Returns true if packets should be dropped + bool ratelimit(int tokens); + + LeakyBucket(); +}; + class ServerUser : public Connection, public User { private: Q_OBJECT @@ -119,6 +146,8 @@ QMap qmTargetCache; QMap qmWhisperRedirect; + LeakyBucket leakyBucket; + int iLastPermissionCheck; QMap qmPermissionSent; #ifdef Q_OS_UNIX