From 0daec57f5cfc4225aa4527b537b4ec4fbbc35635 Mon Sep 17 00:00:00 2001 From: MadMaurice Date: Thu, 30 Aug 2018 15:08:01 +0200 Subject: [PATCH] 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. --- src/murmur/Messages.cpp | 17 ++++++++++++ src/murmur/ServerUser.cpp | 58 +++++++++++++++++++++++++++++++++++++++ src/murmur/ServerUser.h | 29 ++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/src/murmur/Messages.cpp b/src/murmur/Messages.cpp index 967cff794..1739378e1 100644 --- a/src/murmur/Messages.cpp +++ b/src/murmur/Messages.cpp @@ -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; \ @@ -679,6 +684,10 @@ bBroadcast = true; } + if (uSource == pDstServerUser) { + RATELIMIT(uSource); + } + if (msg.has_channel_id()) { Channel *c = qhChannels.value(msg.channel_id()); @@ -791,6 +800,8 @@ c = qhChannels.value(msg.channel_id()); if (! c) return; + } else { + RATELIMIT(uSource); } // Check if the parent exists @@ -1074,6 +1076,8 @@ QSet users; QQueue q; + RATELIMIT(uSource); + QString text = u8(msg.message()); bool changed = false; @@ -1241,6 +1254,8 @@ void Server::msgACL(ServerUser *uSource, MumbleProto::ACL &msg) { return; } + RATELIMIT(uSource); + if (msg.has_query() && msg.query()) { QStack chans; Channel *p; @@ -1497,6 +1512,8 @@ void Server::msgContextAction(ServerUser *uSource, MumbleProto::ContextAction &m } void Server::msgVersion(ServerUser *uSource, MumbleProto::Version &msg) { + RATELIMIT(uSource); + if (msg.has_version()) uSource->uiVersion=msg.version(); if (msg.has_release()) diff --git a/src/murmur/ServerUser.cpp b/src/murmur/ServerUser.cpp index c851d86d8..e5c570d47 100644 --- a/src/murmur/ServerUser.cpp +++ b/src/murmur/ServerUser.cpp @@ -112,3 +112,61 @@ int BandwidthRecord::bandwidth() const { 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 --git a/src/murmur/ServerUser.h b/src/murmur/ServerUser.h index 28e582739..0a3828205 100644 --- a/src/murmur/ServerUser.h +++ b/src/murmur/ServerUser.h @@ -14,6 +14,13 @@ #include #endif +// was introduced in C++11 +#if __cplusplus > 199711LL +#include +#else +#include +#endif + #include "Connection.h" #include "Timer.h" #include "User.h" @@ -55,6 +62,26 @@ struct WhisperTarget { 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 @@ -103,6 +130,8 @@ class ServerUser : public Connection, public User { QMap qmTargetCache; QMap qmWhisperRedirect; + LeakyBucket leakyBucket; + int iLastPermissionCheck; QMap qmPermissionSent; #ifdef Q_OS_UNIX