CVE-2021-21274 (Python, published 2021-02-26T18:15Z)
Description: value: Synapse is a Matrix reference homeserver written in python (pypi package matrix-synapse). Matrix is an ecosystem for open federated Instant Messaging and VoIP. In Synapse before version 1.25.0, a malicious homeserver could redirect requests to their .well-known file to a large file. This can lead to a denial of service attack where homeservers will consume significantly more resources when requesting the .well-known file of a malicious homeserver. This affects any server which accepts federation requests from untrusted servers. Issue is resolved in version 1.25.0. As a workaround the `federation_domain_whitelist` setting can be used to restrict the homeservers communicated with over federation.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-770 \u2014 Allocation of Resources Without Limits or Throttling
The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
Repository: https://github.com/matrix-org/synapse
Files changed:
changelog.d/8950.misc
synapse/http/client.py
synapse/http/federation/well_known_resolver.py
synapse/http/matrixfederationclient.py
tests/http/federation/test_matrix_federation_agent.py
Commit message: Add a maximum size for well-known lookups. (#8950)



Vulnerable code:
                readBodyToFile(response, output_stream, max_size)
        except SynapseError:
            # This can happen e.g. because the body is too large.
            raise
class _ReadBodyToFileProtocol(protocol.Protocol):
            self.deferred.errback(
                SynapseError(
                    502,
                    "Requested file is too large > %r bytes" % (self.max_size,),
                    Codes.TOO_LARGE,
                )
            )
def readBodyToFile(
    response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
from twisted.web.client import RedirectAgent, readBody
                body = await make_deferred_yieldable(readBody(response))
    readBodyToFile,
            d = readBodyToFile(response, output_stream, max_size)

Fixed code:
Add a maximum size of 50 kilobytes to .well-known lookups.
                read_body_with_max_size(response, output_stream, max_size)
            )
        except BodyExceededMaxSize:
            SynapseError(
                502,
                "Requested file is too large > %r bytes" % (max_size,),
                Codes.TOO_LARGE,
class BodyExceededMaxSize(Exception):
    """The maximum allowed size of the HTTP body was exceeded."""


class _ReadBodyWithMaxSizeProtocol(protocol.Protocol):
            self.deferred.errback(BodyExceededMaxSize())
def read_body_with_max_size(
    If the maximum file size is reached, the returned Deferred will resolve to a
    Failure with a BodyExceededMaxSize exception.

    response.deliverBody(_ReadBodyWithMaxSizeProtocol(stream, d, max_size))
from io import BytesIO
from twisted.web.client import RedirectAgent
from synapse.http.client import BodyExceededMaxSize, read_body_with_max_size
# The maximum size (in bytes) to allow a well-known file to be.
WELL_KNOWN_MAX_SIZE = 50 * 1024  # 50 KiB

        Raises:
            _FetchWellKnownFailure if we fail to lookup a result

                body_stream = BytesIO()
                await make_deferred_yieldable(
                    read_body_with_max_size(response, body_stream, WELL_KNOWN_MAX_SIZE)
                )
                body = body_stream.getvalue()
            except BodyExceededMaxSize:
                # If the well-known file was too large, do not keep attempting
                # to download it, but consider it a temporary error.
                logger.warning(
                    "Requested .well-known file for %s is too large > %r bytes",
                    server_name.decode("ascii"),
                    WELL_KNOWN_MAX_SIZE,
                )
                raise _FetchWellKnownFailure(temporary=True)
    Codes,
    SynapseError,
    BodyExceededMaxSize,
    read_body_with_max_size,
            d = read_body_with_max_size(response, output_stream, max_size)
        except BodyExceededMaxSize:
            msg = "Requested file is too large > %r bytes" % (max_size,)
            logger.warning(
                "{%s} [%s] %s", request.txn_id, request.destination, msg,
            )
            SynapseError(502, msg, Codes.TOO_LARGE)
    WELL_KNOWN_MAX_SIZE,
    def test_well_known_too_large(self):
        """A well-known query that returns a result which is too large should be rejected."""
        self.reactor.lookups["testserv"] = "1.2.3.4"

        fetch_d = defer.ensureDeferred(
            self.well_known_resolver.get_well_known(b"testserv")
        )

        # there should be an attempt to connect on port 443 for the .well-known
        clients = self.reactor.tcpClients
        self.assertEqual(len(clients), 1)
        (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
        self.assertEqual(host, "1.2.3.4")
        self.assertEqual(port, 443)

        self._handle_well_known_connection(
            client_factory,
            expected_sni=b"testserv",
            response_headers={b"Cache-Control": b"max-age=1000"},
            content=b'{ "m.server": "' + (b"a" * WELL_KNOWN_MAX_SIZE) + b'" }',
        )

        # The result is sucessful, but disabled delegation.
        r = self.successResultOf(fetch_d)
        self.assertIsNone(r.delegated_server)