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

Commit 6e8fb42

Browse files
authored
Improve validation for send_{join,leave,knock} (#10225)
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.
1 parent bd4919f commit 6e8fb42

File tree

6 files changed

+132
-183
lines changed

6 files changed

+132
-183
lines changed

changelog.d/10225.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve validation on federation `send_{join,leave,knock}` endpoints.

synapse/federation/federation_server.py

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from twisted.internet.abstract import isIPAddress
3535
from twisted.python import failure
3636

37-
from synapse.api.constants import EduTypes, EventTypes
37+
from synapse.api.constants import EduTypes, EventTypes, Membership
3838
from synapse.api.errors import (
3939
AuthError,
4040
Codes,
@@ -46,6 +46,7 @@
4646
)
4747
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
4848
from synapse.events import EventBase
49+
from synapse.events.snapshot import EventContext
4950
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
5051
from synapse.federation.persistence import TransactionActions
5152
from synapse.federation.units import Edu, Transaction
@@ -537,26 +538,21 @@ async def on_invite_request(
537538
return {"event": ret_pdu.get_pdu_json(time_now)}
538539

539540
async def on_send_join_request(
540-
self, origin: str, content: JsonDict
541+
self, origin: str, content: JsonDict, room_id: str
541542
) -> Dict[str, Any]:
542-
logger.debug("on_send_join_request: content: %s", content)
543-
544-
assert_params_in_dict(content, ["room_id"])
545-
room_version = await self.store.get_room_version(content["room_id"])
546-
pdu = event_from_pdu_json(content, room_version)
547-
548-
origin_host, _ = parse_server_name(origin)
549-
await self.check_server_matches_acl(origin_host, pdu.room_id)
550-
551-
logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures)
543+
context = await self._on_send_membership_event(
544+
origin, content, Membership.JOIN, room_id
545+
)
552546

553-
pdu = await self._check_sigs_and_hash(room_version, pdu)
547+
prev_state_ids = await context.get_prev_state_ids()
548+
state_ids = list(prev_state_ids.values())
549+
auth_chain = await self.store.get_auth_chain(room_id, state_ids)
550+
state = await self.store.get_events(state_ids)
554551

555-
res_pdus = await self.handler.on_send_join_request(origin, pdu)
556552
time_now = self._clock.time_msec()
557553
return {
558-
"state": [p.get_pdu_json(time_now) for p in res_pdus["state"]],
559-
"auth_chain": [p.get_pdu_json(time_now) for p in res_pdus["auth_chain"]],
554+
"state": [p.get_pdu_json(time_now) for p in state.values()],
555+
"auth_chain": [p.get_pdu_json(time_now) for p in auth_chain],
560556
}
561557

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

574-
async def on_send_leave_request(self, origin: str, content: JsonDict) -> dict:
570+
async def on_send_leave_request(
571+
self, origin: str, content: JsonDict, room_id: str
572+
) -> dict:
575573
logger.debug("on_send_leave_request: content: %s", content)
576-
577-
assert_params_in_dict(content, ["room_id"])
578-
room_version = await self.store.get_room_version(content["room_id"])
579-
pdu = event_from_pdu_json(content, room_version)
580-
581-
origin_host, _ = parse_server_name(origin)
582-
await self.check_server_matches_acl(origin_host, pdu.room_id)
583-
584-
logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures)
585-
586-
pdu = await self._check_sigs_and_hash(room_version, pdu)
587-
588-
await self.handler.on_send_leave_request(origin, pdu)
574+
await self._on_send_membership_event(origin, content, Membership.LEAVE, room_id)
589575
return {}
590576

591577
async def on_make_knock_request(
@@ -651,39 +637,76 @@ async def on_send_knock_request(
651637
Returns:
652638
The stripped room state.
653639
"""
654-
logger.debug("on_send_knock_request: content: %s", content)
640+
event_context = await self._on_send_membership_event(
641+
origin, content, Membership.KNOCK, room_id
642+
)
643+
644+
# Retrieve stripped state events from the room and send them back to the remote
645+
# server. This will allow the remote server's clients to display information
646+
# related to the room while the knock request is pending.
647+
stripped_room_state = (
648+
await self.store.get_stripped_room_state_from_event_context(
649+
event_context, self._room_prejoin_state_types
650+
)
651+
)
652+
return {"knock_state_events": stripped_room_state}
653+
654+
async def _on_send_membership_event(
655+
self, origin: str, content: JsonDict, membership_type: str, room_id: str
656+
) -> EventContext:
657+
"""Handle an on_send_{join,leave,knock} request
658+
659+
Does some preliminary validation before passing the request on to the
660+
federation handler.
661+
662+
Args:
663+
origin: The (authenticated) requesting server
664+
content: The body of the send_* request - a complete membership event
665+
membership_type: The expected membership type (join or leave, depending
666+
on the endpoint)
667+
room_id: The room_id from the request, to be validated against the room_id
668+
in the event
669+
670+
Returns:
671+
The context of the event after inserting it into the room graph.
672+
673+
Raises:
674+
SynapseError if there is a problem with the request, including things like
675+
the room_id not matching or the event not being authorized.
676+
"""
677+
assert_params_in_dict(content, ["room_id"])
678+
if content["room_id"] != room_id:
679+
raise SynapseError(
680+
400,
681+
"Room ID in body does not match that in request path",
682+
Codes.BAD_JSON,
683+
)
655684

656685
room_version = await self.store.get_room_version(room_id)
657686

658-
# Check that this room supports knocking as defined by its room version
659-
if not room_version.msc2403_knocking:
687+
if membership_type == Membership.KNOCK and not room_version.msc2403_knocking:
660688
raise SynapseError(
661689
403,
662690
"This room version does not support knocking",
663691
errcode=Codes.FORBIDDEN,
664692
)
665693

666-
pdu = event_from_pdu_json(content, room_version)
694+
event = event_from_pdu_json(content, room_version)
667695

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

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

673-
pdu = await self._check_sigs_and_hash(room_version, pdu)
702+
origin_host, _ = parse_server_name(origin)
703+
await self.check_server_matches_acl(origin_host, event.room_id)
674704

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

678-
# Retrieve stripped state events from the room and send them back to the remote
679-
# server. This will allow the remote server's clients to display information
680-
# related to the room while the knock request is pending.
681-
stripped_room_state = (
682-
await self.store.get_stripped_room_state_from_event_context(
683-
event_context, self._room_prejoin_state_types
684-
)
685-
)
686-
return {"knock_state_events": stripped_room_state}
707+
event = await self._check_sigs_and_hash(room_version, event)
708+
709+
return await self.handler.on_send_membership_event(origin, event)
687710

688711
async def on_event_auth(
689712
self, origin: str, room_id: str, event_id: str

synapse/federation/transport/server.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ class FederationV1SendLeaveServlet(BaseFederationServerServlet):
553553
PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
554554

555555
async def on_PUT(self, origin, content, query, room_id, event_id):
556-
content = await self.handler.on_send_leave_request(origin, content)
556+
content = await self.handler.on_send_leave_request(origin, content, room_id)
557557
return 200, (200, content)
558558

559559

@@ -563,7 +563,7 @@ class FederationV2SendLeaveServlet(BaseFederationServerServlet):
563563
PREFIX = FEDERATION_V2_PREFIX
564564

565565
async def on_PUT(self, origin, content, query, room_id, event_id):
566-
content = await self.handler.on_send_leave_request(origin, content)
566+
content = await self.handler.on_send_leave_request(origin, content, room_id)
567567
return 200, content
568568

569569

@@ -602,9 +602,9 @@ class FederationV1SendJoinServlet(BaseFederationServerServlet):
602602
PATH = "/send_join/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
603603

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

610610

@@ -614,9 +614,9 @@ class FederationV2SendJoinServlet(BaseFederationServerServlet):
614614
PREFIX = FEDERATION_V2_PREFIX
615615

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

622622

0 commit comments

Comments
 (0)