Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Improve validation for send_{join,leave,knock} (#10225)
Browse files Browse the repository at this point in the history
The idea here is to stop people sending things that aren't joins/leaves/knocks through these endpoints: previously you could send anything you liked through them. I wasn't able to find any security holes from doing so, but it doesn't sound like a good thing.
  • Loading branch information
richvdh authored Jun 24, 2021
1 parent bd4919f commit 6e8fb42
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 183 deletions.
1 change: 1 addition & 0 deletions changelog.d/10225.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve validation on federation `send_{join,leave,knock}` endpoints.
121 changes: 72 additions & 49 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from twisted.internet.abstract import isIPAddress
from twisted.python import failure

from synapse.api.constants import EduTypes, EventTypes
from synapse.api.constants import EduTypes, EventTypes, Membership
from synapse.api.errors import (
AuthError,
Codes,
Expand All @@ -46,6 +46,7 @@
)
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.events import EventBase
from synapse.events.snapshot import EventContext
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
from synapse.federation.persistence import TransactionActions
from synapse.federation.units import Edu, Transaction
Expand Down Expand Up @@ -537,26 +538,21 @@ async def on_invite_request(
return {"event": ret_pdu.get_pdu_json(time_now)}

async def on_send_join_request(
self, origin: str, content: JsonDict
self, origin: str, content: JsonDict, room_id: str
) -> Dict[str, Any]:
logger.debug("on_send_join_request: content: %s", content)

assert_params_in_dict(content, ["room_id"])
room_version = await self.store.get_room_version(content["room_id"])
pdu = event_from_pdu_json(content, room_version)

origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, pdu.room_id)

logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures)
context = await self._on_send_membership_event(
origin, content, Membership.JOIN, room_id
)

pdu = await self._check_sigs_and_hash(room_version, pdu)
prev_state_ids = await context.get_prev_state_ids()
state_ids = list(prev_state_ids.values())
auth_chain = await self.store.get_auth_chain(room_id, state_ids)
state = await self.store.get_events(state_ids)

res_pdus = await self.handler.on_send_join_request(origin, pdu)
time_now = self._clock.time_msec()
return {
"state": [p.get_pdu_json(time_now) for p in res_pdus["state"]],
"auth_chain": [p.get_pdu_json(time_now) for p in res_pdus["auth_chain"]],
"state": [p.get_pdu_json(time_now) for p in state.values()],
"auth_chain": [p.get_pdu_json(time_now) for p in auth_chain],
}

async def on_make_leave_request(
Expand All @@ -571,21 +567,11 @@ async def on_make_leave_request(
time_now = self._clock.time_msec()
return {"event": pdu.get_pdu_json(time_now), "room_version": room_version}

async def on_send_leave_request(self, origin: str, content: JsonDict) -> dict:
async def on_send_leave_request(
self, origin: str, content: JsonDict, room_id: str
) -> dict:
logger.debug("on_send_leave_request: content: %s", content)

assert_params_in_dict(content, ["room_id"])
room_version = await self.store.get_room_version(content["room_id"])
pdu = event_from_pdu_json(content, room_version)

origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, pdu.room_id)

logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures)

pdu = await self._check_sigs_and_hash(room_version, pdu)

await self.handler.on_send_leave_request(origin, pdu)
await self._on_send_membership_event(origin, content, Membership.LEAVE, room_id)
return {}

async def on_make_knock_request(
Expand Down Expand Up @@ -651,39 +637,76 @@ async def on_send_knock_request(
Returns:
The stripped room state.
"""
logger.debug("on_send_knock_request: content: %s", content)
event_context = await self._on_send_membership_event(
origin, content, Membership.KNOCK, room_id
)

# Retrieve stripped state events from the room and send them back to the remote
# server. This will allow the remote server's clients to display information
# related to the room while the knock request is pending.
stripped_room_state = (
await self.store.get_stripped_room_state_from_event_context(
event_context, self._room_prejoin_state_types
)
)
return {"knock_state_events": stripped_room_state}

async def _on_send_membership_event(
self, origin: str, content: JsonDict, membership_type: str, room_id: str
) -> EventContext:
"""Handle an on_send_{join,leave,knock} request
Does some preliminary validation before passing the request on to the
federation handler.
Args:
origin: The (authenticated) requesting server
content: The body of the send_* request - a complete membership event
membership_type: The expected membership type (join or leave, depending
on the endpoint)
room_id: The room_id from the request, to be validated against the room_id
in the event
Returns:
The context of the event after inserting it into the room graph.
Raises:
SynapseError if there is a problem with the request, including things like
the room_id not matching or the event not being authorized.
"""
assert_params_in_dict(content, ["room_id"])
if content["room_id"] != room_id:
raise SynapseError(
400,
"Room ID in body does not match that in request path",
Codes.BAD_JSON,
)

room_version = await self.store.get_room_version(room_id)

# Check that this room supports knocking as defined by its room version
if not room_version.msc2403_knocking:
if membership_type == Membership.KNOCK and not room_version.msc2403_knocking:
raise SynapseError(
403,
"This room version does not support knocking",
errcode=Codes.FORBIDDEN,
)

pdu = event_from_pdu_json(content, room_version)
event = event_from_pdu_json(content, room_version)

origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, pdu.room_id)
if event.type != EventTypes.Member or not event.is_state():
raise SynapseError(400, "Not an m.room.member event", Codes.BAD_JSON)

logger.debug("on_send_knock_request: pdu sigs: %s", pdu.signatures)
if event.content.get("membership") != membership_type:
raise SynapseError(400, "Not a %s event" % membership_type, Codes.BAD_JSON)

pdu = await self._check_sigs_and_hash(room_version, pdu)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, event.room_id)

# Handle the event, and retrieve the EventContext
event_context = await self.handler.on_send_knock_request(origin, pdu)
logger.debug("_on_send_membership_event: pdu sigs: %s", event.signatures)

# Retrieve stripped state events from the room and send them back to the remote
# server. This will allow the remote server's clients to display information
# related to the room while the knock request is pending.
stripped_room_state = (
await self.store.get_stripped_room_state_from_event_context(
event_context, self._room_prejoin_state_types
)
)
return {"knock_state_events": stripped_room_state}
event = await self._check_sigs_and_hash(room_version, event)

return await self.handler.on_send_membership_event(origin, event)

async def on_event_auth(
self, origin: str, room_id: str, event_id: str
Expand Down
12 changes: 6 additions & 6 deletions synapse/federation/transport/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ class FederationV1SendLeaveServlet(BaseFederationServerServlet):
PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"

async def on_PUT(self, origin, content, query, room_id, event_id):
content = await self.handler.on_send_leave_request(origin, content)
content = await self.handler.on_send_leave_request(origin, content, room_id)
return 200, (200, content)


Expand All @@ -563,7 +563,7 @@ class FederationV2SendLeaveServlet(BaseFederationServerServlet):
PREFIX = FEDERATION_V2_PREFIX

async def on_PUT(self, origin, content, query, room_id, event_id):
content = await self.handler.on_send_leave_request(origin, content)
content = await self.handler.on_send_leave_request(origin, content, room_id)
return 200, content


Expand Down Expand Up @@ -602,9 +602,9 @@ class FederationV1SendJoinServlet(BaseFederationServerServlet):
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
# TODO(paul): assert that event_id parsed from path actually
# match those given in content
content = await self.handler.on_send_join_request(origin, content)
content = await self.handler.on_send_join_request(origin, content, room_id)
return 200, (200, content)


Expand All @@ -614,9 +614,9 @@ class FederationV2SendJoinServlet(BaseFederationServerServlet):
PREFIX = FEDERATION_V2_PREFIX

async def on_PUT(self, origin, content, query, room_id, event_id):
# TODO(paul): assert that room_id/event_id parsed from path actually
# TODO(paul): assert that event_id parsed from path actually
# match those given in content
content = await self.handler.on_send_join_request(origin, content)
content = await self.handler.on_send_join_request(origin, content, room_id)
return 200, content


Expand Down
Loading

0 comments on commit 6e8fb42

Please sign in to comment.