CVE-2020-26257 (Python, published 2020-12-09T19:15Z)
Description: value: Matrix is an ecosystem for open federated Instant Messaging and VoIP. Synapse is a reference "homeserver" implementation of Matrix. A malicious or poorly-implemented homeserver can inject malformed events into a room by specifying a different room id in the path of a `/send_join`, `/send_leave`, `/invite` or `/exchange_third_party_invite` request. This can lead to a denial of service in which future events will not be correctly sent to other servers over federation. This affects any server which accepts federation requests from untrusted servers. The Matrix Synapse reference implementation before version 1.23.1 the implementation is vulnerable to this injection attack. Issue is fixed in version 1.23.1. As a workaround homeserver administrators could limit access to the federation API to trusted servers (for example via `federation_domain_whitelist`).
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/matrix-org/synapse
Files changed:
changelog.d/8776.bugfix
synapse/federation/federation_server.py
synapse/federation/transport/server.py
synapse/handlers/federation.py
tests/handlers/test_federation.py
Commit message: Consistently use room_id from federation request body (#8776)

* Consistently use room_id from federation request body

Some federation APIs have a redundant `room_id` path param (see
https://github.com/matrix-org/matrix-doc/issues/2330). We should make sure we
consistently use either the path param or the body param, and the body param is
easier.

* Kill off some references to "context"

Once upon a time, "rooms" were known as "contexts". I think this kills of the
last references to "contexts".

Vulnerable code:
    async def on_context_state_request(
        self, origin: str, content: JsonDict, room_id: str
        room_version = await self.store.get_room_version(room_id)
    async def on_send_leave_request(
        self, origin: str, content: JsonDict, room_id: str
    ) -> dict:
        room_version = await self.store.get_room_version(room_id)
    async def on_exchange_third_party_invite_request(
        self, room_id: str, event_dict: Dict
    ):
        ret = await self.handler.on_exchange_third_party_invite_request(
            room_id, event_dict
        )
    PATH = "/state/(?P<context>[^/]*)/?"
    # This is when someone asks for all data for a given context.
    async def on_GET(self, origin, content, query, context):
        return await self.handler.on_context_state_request(
            context,
    PATH = "/backfill/(?P<context>[^/]*)/?"
    async def on_GET(self, origin, content, query, context):
        return await self.handler.on_backfill_request(origin, context, versions, limit)
    PATH = "/make_join/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
    async def on_GET(self, origin, _content, query, context, user_id):
            origin, context, user_id, supported_versions=supported_versions
    PATH = "/make_leave/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
    async def on_GET(self, origin, content, query, context, user_id):
        content = await self.handler.on_make_leave_request(origin, context, user_id)
        content = await self.handler.on_send_leave_request(origin, content, room_id)
        content = await self.handler.on_send_leave_request(origin, content, room_id)
    PATH = "/event_auth/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
    async def on_GET(self, origin, content, query, context, event_id):
        return await self.handler.on_event_auth(origin, context, event_id)
    PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, context, event_id):
        # TODO(paul): assert that context/event_id parsed from path actually
        content = await self.handler.on_send_join_request(origin, content, context)
    PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, context, event_id):
        # TODO(paul): assert that context/event_id parsed from path actually
        content = await self.handler.on_send_join_request(origin, content, context)
    PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, context, event_id):
    PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, context, event_id):
        # TODO(paul): assert that context/event_id parsed from path actually
        content = await self.handler.on_exchange_third_party_invite_request(
            room_id, content
        )
        self, room_id: str, event_dict: JsonDict
            room_id: The ID of the room.

            event_dict (dict[str, Any]): Dictionary containing the event body.
        room_version = await self.store.get_room_version_id(room_id)
            room_id=room_id,

Fixed code:
Fix a bug in some federation APIs which could lead to unexpected behaviour if different parameters were set in the URI and the request body.
from synapse.http.servlet import assert_params_in_dict
    async def on_room_state_request(
        self, origin: str, content: JsonDict
        assert_params_in_dict(content, ["room_id"])
        room_version = await self.store.get_room_version(content["room_id"])
    async def on_send_leave_request(self, origin: str, content: JsonDict) -> dict:
        assert_params_in_dict(content, ["room_id"])
        room_version = await self.store.get_room_version(content["room_id"])
    async def on_exchange_third_party_invite_request(self, event_dict: Dict):
        ret = await self.handler.on_exchange_third_party_invite_request(event_dict)
    PATH = "/state/(?P<room_id>[^/]*)/?"
    # This is when someone asks for all data for a given room.
    async def on_GET(self, origin, content, query, room_id):
        return await self.handler.on_room_state_request(
            room_id,
    PATH = "/backfill/(?P<room_id>[^/]*)/?"
    async def on_GET(self, origin, content, query, room_id):
        return await self.handler.on_backfill_request(origin, room_id, versions, limit)
    PATH = "/make_join/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
    async def on_GET(self, origin, _content, query, room_id, user_id):
            origin, room_id, user_id, supported_versions=supported_versions
    PATH = "/make_leave/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
    async def on_GET(self, origin, content, query, room_id, user_id):
        content = await self.handler.on_make_leave_request(origin, room_id, user_id)
        content = await self.handler.on_send_leave_request(origin, content)
        content = await self.handler.on_send_leave_request(origin, content)
    PATH = "/event_auth/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
    async def on_GET(self, origin, content, query, room_id, event_id):
        return await self.handler.on_event_auth(origin, room_id, event_id)
    PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, room_id, event_id):
        # TODO(paul): assert that room_id/event_id parsed from path actually
        content = await self.handler.on_send_join_request(origin, content)
    PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, room_id, event_id):
        # TODO(paul): assert that room_id/event_id parsed from path actually
        content = await self.handler.on_send_join_request(origin, content)
    PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, room_id, event_id):
    PATH = "/invite/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
    async def on_PUT(self, origin, content, query, room_id, event_id):
        # TODO(paul): assert that room_id/event_id parsed from path actually
        content = await self.handler.on_exchange_third_party_invite_request(content)
from synapse.http.servlet import assert_params_in_dict
        self, event_dict: JsonDict
            event_dict: Dictionary containing the event body.
        assert_params_in_dict(event_dict, ["room_id"])
        room_version = await self.store.get_room_version_id(event_dict["room_id"])