From 89071a1a754c69a50deac89e6bb74002d4cda19d Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Tue, 6 Apr 2021 14:10:08 +0000 Subject: [PATCH] Limit maximum request size. Request size shouldn't be unbounded because there is no need for it and it can lead to DoS in some cases. --- sydent/http/httpcommon.py | 23 ++++++++++++++++++++--- sydent/http/httpserver.py | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/sydent/http/httpcommon.py b/sydent/http/httpcommon.py index 9dde0ec1..2088dd54 100644 --- a/sydent/http/httpcommon.py +++ b/sydent/http/httpcommon.py @@ -23,9 +23,15 @@ from twisted.web._newclient import ResponseDone from twisted.web.http import PotentialDataLoss from twisted.web.iweb import UNKNOWN_LENGTH +from twisted.web import server + logger = logging.getLogger(__name__) +# Arbitrarily limited to 512 KiB. +MAX_REQUEST_SIZE = 512 * 1024 + + class SslComponents: def __init__(self, sydent): self.sydent = sydent @@ -61,7 +67,7 @@ def makeTrustRoot(self): fp = open(caCertFilename) caCert = twisted.internet.ssl.Certificate.loadPEM(fp.read()) fp.close() - except: + except Exception: logger.warn("Failed to open CA cert file %s", caCertFilename) raise logger.warn("Using custom CA cert file: %s", caCertFilename) @@ -70,7 +76,6 @@ def makeTrustRoot(self): return twisted.internet.ssl.OpenSSLDefaultPaths() - class BodyExceededMaxSize(Exception): """The maximum allowed size of the HTTP body was exceeded.""" @@ -123,7 +128,7 @@ def dataReceived(self, data) -> None: # discarded anyway. self.transport.abortConnection() - def connectionLost(self, reason = connectionDone) -> None: + def connectionLost(self, reason=connectionDone) -> None: # If the maximum size was already exceeded, there's nothing to do. if self.deferred.called: return @@ -163,3 +168,15 @@ def read_body_with_max_size(response, max_size): response.deliverBody(_ReadBodyWithMaxSizeProtocol(d, max_size)) return d + + +class SizeLimitingRequest(server.Request): + def handleContentChunk(self, data): + if self.content.tell() + len(data) > MAX_REQUEST_SIZE: + logger.info( + "Aborting connection from %s because the request exceeds maximum size", + self.client.host) + self.transport.abortConnection() + return + + return super().handleContentChunk(data) diff --git a/sydent/http/httpserver.py b/sydent/http/httpserver.py index 245f3574..c40ef4cf 100644 --- a/sydent/http/httpserver.py +++ b/sydent/http/httpserver.py @@ -29,6 +29,7 @@ from sydent.http.servlets.authenticated_unbind_threepid_servlet import ( AuthenticatedUnbindThreePidServlet, ) +from sydent.http.httpcommon import SizeLimitingRequest logger = logging.getLogger(__name__) @@ -129,6 +130,7 @@ def __init__(self, sydent): v2.putChild(b'hash_details', self.sydent.servlets.hash_details) self.factory = Site(root) + self.factory.requestFactory = SizeLimitingRequest self.factory.displayTracebacks = False def setup(self):