Browse Source

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.
Denis Kasak 3 years ago
parent
commit
89071a1a75
2 changed files with 22 additions and 3 deletions
  1. 20 3
      sydent/http/httpcommon.py
  2. 2 0
      sydent/http/httpserver.py

+ 20 - 3
sydent/http/httpcommon.py

@@ -23,9 +23,15 @@ from twisted.internet.protocol import connectionDone
 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 @@ class SslComponents:
                 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 @@ class SslComponents:
             return twisted.internet.ssl.OpenSSLDefaultPaths()
 
 
-
 class BodyExceededMaxSize(Exception):
     """The maximum allowed size of the HTTP body was exceeded."""
 
@@ -123,7 +128,7 @@ class _ReadBodyWithMaxSizeProtocol(protocol.Protocol):
             # 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)

+ 2 - 0
sydent/http/httpserver.py

@@ -29,6 +29,7 @@ from sydent.http.servlets.authenticated_bind_threepid_servlet import (
 from sydent.http.servlets.authenticated_unbind_threepid_servlet import (
     AuthenticatedUnbindThreePidServlet,
 )
+from sydent.http.httpcommon import SizeLimitingRequest
 
 logger = logging.getLogger(__name__)
 
@@ -129,6 +130,7 @@ class ClientApiHttpServer:
         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):