VYPR
Moderate severityNVD Advisory· Published Feb 26, 2021· Updated Aug 3, 2024

Denial of service attack via .well-known lookups

CVE-2021-21274

Description

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In Synapse before 1.25.0, a malicious homeserver could redirect .well-known requests to a large file, causing resource exhaustion and denial of service.

Vulnerability

CVE-2021-21274 is a denial-of-service vulnerability in Synapse, the reference Matrix homeserver written in Python. The root cause is that Synapse did not enforce a maximum size when fetching the .well-known file of a remote homeserver during federation. A malicious homeserver could redirect these requests to an arbitrarily large file, causing the requesting server to consume excessive resources (bandwidth, memory, CPU) while downloading the payload [1][2].

Exploitation

An attacker controls a homeserver that is federated with the target server. When the target server attempts to resolve the attacker's .well-known URI (e.g., during a federation handshake), the attacker's server responds with a redirect to a large file. No authentication is required beyond the ability to federate; any server that accepts federation requests from untrusted servers is affected [2].

Impact

Successful exploitation leads to a denial-of-service condition on the requesting homeserver. The server may become unresponsive or crash due to resource exhaustion, disrupting normal Matrix communication for its users [2].

Mitigation

The issue is fixed in Synapse version 1.25.0, which introduces a maximum size limit for .well-known lookups [1][3]. As a workaround, administrators can restrict federation to a whitelist of trusted domains using the federation_domain_whitelist configuration option, preventing interaction with untrusted servers [2].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
matrix-synapsePyPI
>= 0.99.0, < 1.25.01.25.0

Affected products

2

Patches

1
ff5c4da1289c

Add a maximum size for well-known lookups. (#8950)

https://github.com/matrix-org/synapsePatrick ClokeDec 16, 2020via ghsa
5 files changed · +80 18
  • changelog.d/8950.misc+1 0 added
    @@ -0,0 +1 @@
    +Add a maximum size of 50 kilobytes to .well-known lookups.
    
  • synapse/http/client.py+18 14 modified
    @@ -720,11 +720,14 @@ async def get_file(
     
             try:
                 length = await make_deferred_yieldable(
    -                readBodyToFile(response, output_stream, max_size)
    +                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,
                 )
    -        except SynapseError:
    -            # This can happen e.g. because the body is too large.
    -            raise
             except Exception as e:
                 raise SynapseError(502, ("Failed to download remote body: %s" % e)) from e
     
    @@ -748,7 +751,11 @@ def _timeout_to_request_timed_out_error(f: Failure):
         return f
     
     
    -class _ReadBodyToFileProtocol(protocol.Protocol):
    +class BodyExceededMaxSize(Exception):
    +    """The maximum allowed size of the HTTP body was exceeded."""
    +
    +
    +class _ReadBodyWithMaxSizeProtocol(protocol.Protocol):
         def __init__(
             self, stream: BinaryIO, deferred: defer.Deferred, max_size: Optional[int]
         ):
    @@ -761,13 +768,7 @@ def dataReceived(self, data: bytes) -> None:
             self.stream.write(data)
             self.length += len(data)
             if self.max_size is not None and self.length >= self.max_size:
    -            self.deferred.errback(
    -                SynapseError(
    -                    502,
    -                    "Requested file is too large > %r bytes" % (self.max_size,),
    -                    Codes.TOO_LARGE,
    -                )
    -            )
    +            self.deferred.errback(BodyExceededMaxSize())
                 self.deferred = defer.Deferred()
                 self.transport.loseConnection()
     
    @@ -782,12 +783,15 @@ def connectionLost(self, reason: Failure) -> None:
                 self.deferred.errback(reason)
     
     
    -def readBodyToFile(
    +def read_body_with_max_size(
         response: IResponse, stream: BinaryIO, max_size: Optional[int]
     ) -> defer.Deferred:
         """
         Read a HTTP response body to a file-object. Optionally enforcing a maximum file size.
     
    +    If the maximum file size is reached, the returned Deferred will resolve to a
    +    Failure with a BodyExceededMaxSize exception.
    +
         Args:
             response: The HTTP response to read from.
             stream: The file-object to write to.
    @@ -798,7 +802,7 @@ def readBodyToFile(
         """
     
         d = defer.Deferred()
    -    response.deliverBody(_ReadBodyToFileProtocol(stream, d, max_size))
    +    response.deliverBody(_ReadBodyWithMaxSizeProtocol(stream, d, max_size))
         return d
     
     
    
  • synapse/http/federation/well_known_resolver.py+23 2 modified
    @@ -15,17 +15,19 @@
     import logging
     import random
     import time
    +from io import BytesIO
     from typing import Callable, Dict, Optional, Tuple
     
     import attr
     
     from twisted.internet import defer
     from twisted.internet.interfaces import IReactorTime
    -from twisted.web.client import RedirectAgent, readBody
    +from twisted.web.client import RedirectAgent
     from twisted.web.http import stringToDatetime
     from twisted.web.http_headers import Headers
     from twisted.web.iweb import IAgent, IResponse
     
    +from synapse.http.client import BodyExceededMaxSize, read_body_with_max_size
     from synapse.logging.context import make_deferred_yieldable
     from synapse.util import Clock, json_decoder
     from synapse.util.caches.ttlcache import TTLCache
    @@ -53,6 +55,9 @@
     # lower bound for .well-known cache period
     WELL_KNOWN_MIN_CACHE_PERIOD = 5 * 60
     
    +# The maximum size (in bytes) to allow a well-known file to be.
    +WELL_KNOWN_MAX_SIZE = 50 * 1024  # 50 KiB
    +
     # Attempt to refetch a cached well-known N% of the TTL before it expires.
     # e.g. if set to 0.2 and we have a cached entry with a TTL of 5mins, then
     # we'll start trying to refetch 1 minute before it expires.
    @@ -229,6 +234,9 @@ async def _make_well_known_request(
                 server_name: name of the server, from the requested url
                 retry: Whether to retry the request if it fails.
     
    +        Raises:
    +            _FetchWellKnownFailure if we fail to lookup a result
    +
             Returns:
                 Returns the response object and body. Response may be a non-200 response.
             """
    @@ -250,7 +258,11 @@ async def _make_well_known_request(
                             b"GET", uri, headers=Headers(headers)
                         )
                     )
    -                body = await make_deferred_yieldable(readBody(response))
    +                body_stream = BytesIO()
    +                await make_deferred_yieldable(
    +                    read_body_with_max_size(response, body_stream, WELL_KNOWN_MAX_SIZE)
    +                )
    +                body = body_stream.getvalue()
     
                     if 500 <= response.code < 600:
                         raise Exception("Non-200 response %s" % (response.code,))
    @@ -259,6 +271,15 @@ async def _make_well_known_request(
                 except defer.CancelledError:
                     # Bail if we've been cancelled
                     raise
    +            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)
                 except Exception as e:
                     if not retry or i >= WELL_KNOWN_RETRY_ATTEMPTS:
                         logger.info("Error fetching %s: %s", uri_str, e)
    
  • synapse/http/matrixfederationclient.py+11 2 modified
    @@ -37,16 +37,19 @@
     import synapse.metrics
     import synapse.util.retryutils
     from synapse.api.errors import (
    +    Codes,
         FederationDeniedError,
         HttpResponseException,
         RequestSendFailed,
    +    SynapseError,
     )
     from synapse.http import QuieterFileBodyProducer
     from synapse.http.client import (
         BlacklistingAgentWrapper,
         BlacklistingReactorWrapper,
    +    BodyExceededMaxSize,
         encode_query_args,
    -    readBodyToFile,
    +    read_body_with_max_size,
     )
     from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
     from synapse.logging.context import make_deferred_yieldable
    @@ -975,9 +978,15 @@ async def get_file(
             headers = dict(response.headers.getAllRawHeaders())
     
             try:
    -            d = readBodyToFile(response, output_stream, max_size)
    +            d = read_body_with_max_size(response, output_stream, max_size)
                 d.addTimeout(self.default_timeout, self.reactor)
                 length = await make_deferred_yieldable(d)
    +        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)
             except Exception as e:
                 logger.warning(
                     "{%s} [%s] Error reading response: %s",
    
  • tests/http/federation/test_matrix_federation_agent.py+27 0 modified
    @@ -36,6 +36,7 @@
     from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
     from synapse.http.federation.srv_resolver import Server
     from synapse.http.federation.well_known_resolver import (
    +    WELL_KNOWN_MAX_SIZE,
         WellKnownResolver,
         _cache_period_from_headers,
     )
    @@ -1107,6 +1108,32 @@ def test_well_known_cache_with_temp_failure(self):
             r = self.successResultOf(fetch_d)
             self.assertEqual(r.delegated_server, None)
     
    +    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)
    +
         def test_srv_fallbacks(self):
             """Test that other SRV results are tried if the first one fails.
             """
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.