Block other federation endpoints for MSC4242 rooms; ensure the join event is connected to the state DAG

This PR only handles /send_join and connected events in /send,
so everything else should return early with coherent error messages.

The join event must be connected to the main state DAG but we previously
didn't ensure this.
This commit is contained in:
Kegan Dougal authored and GitHub committed 2026-10-02 16:38:34 +01:00
1 parent 55ad431e26
commit fd9a6a42c2
3 files changed
+51 -1

No files matched your search

+21
View File
@@ -233,12 +233,22 @@ class FederationServer(FederationBase):
# We pause a bit so that we don't start handling all rooms at once.
await self._clock.sleep(Duration(seconds=random.uniform(0, 0.1)))
async def _assert_not_state_dag_room(self, room_id: str, endpoint: str) -> None:
room_version = await self.store.get_room_version(room_id)
if room_version.msc4242_state_dags:
raise SynapseError(
500,
f"{endpoint} is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNKNOWN,
)
async def on_backfill_request(
self, origin: str, room_id: str, versions: list[str], limit: int
) -> tuple[int, dict[str, Any]]:
async with self._server_linearizer.queue((origin, room_id)):
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/backfill")
pdus = await self.handler.on_backfill_request(
origin, room_id, versions, limit
@@ -654,6 +664,7 @@ class FederationServer(FederationBase):
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/state")
# we grab the linearizer to protect ourselves from servers which hammer
# us. In theory we might already have the response to this query
@@ -694,6 +705,7 @@ class FederationServer(FederationBase):
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/state_ids")
resp = await self._state_ids_resp_cache.wrap(
(room_id, event_id),
@@ -1154,6 +1166,13 @@ class FederationServer(FederationBase):
# Note that get_room_version throws if the room does not exist here.
room_version = await self.store.get_room_version(room_id)
if room_version.msc4242_state_dags and membership_type != Membership.JOIN:
raise SynapseError(
500,
f"/send_{membership_type} is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNKNOWN,
)
if await self.store.is_partial_state_room(room_id):
# If our server is still only partially joined, we can't give a complete
# response to /send_join, /send_knock or /send_leave.
@@ -1247,6 +1266,7 @@ class FederationServer(FederationBase):
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/event_auth")
time_now = self._clock.time_msec()
auth_pdus = await self.handler.on_event_auth(event_id, room_id)
@@ -1332,6 +1352,7 @@ class FederationServer(FederationBase):
origin, room_id, earliest_events, latest_events, limit
)
else:
await self._assert_not_state_dag_room(room_id, "/get_missing_events")
missing_events = await self.handler.on_get_missing_events(
origin, room_id, earliest_events, latest_events, limit
)
+10
View File
@@ -254,6 +254,9 @@ class FederationHandler:
Returns:
True if we actually tried to backfill something, otherwise False.
"""
if (await self.store.get_room_version(room_id)).msc4242_state_dags:
return False
# Starting the processing time here so we can include the room backfill
# linearizer lock queue in the timing
processing_start_time = self.clock.time_msec() if record_time else 0
@@ -1459,6 +1462,13 @@ class FederationHandler:
if event.state_key is None:
raise SynapseError(400, "The invite event did not have a state key")
if room_version.msc4242_state_dags:
raise SynapseError(
500,
"/invite is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNKNOWN,
)
is_blocked = await self.store.is_room_blocked(event.room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")
+20 -1
View File
@@ -559,7 +559,11 @@ class FederationEventHandler:
raise SynapseError(400, "Room version mismatch")
if room_version.msc4242_state_dags and not is_state_dag_connected(
[ev for ev in state if supports_msc4242_state_dag(ev)]
[
ev
for ev in itertools.chain(state, [event])
if supports_msc4242_state_dag(ev)
]
):
# We should have been given a connected state DAG. If there is a gap in it we
# cannot calculate the state, so the response is invalid and we refuse the join.
@@ -1256,6 +1260,15 @@ class FederationEventHandler:
if not missing_prevs:
return await self._state_handler.compute_event_context(event)
if event.room_version.msc4242_state_dags:
raise FederationError(
"ERROR",
500,
"Fetching the state at missing prev_events is not implemented for "
"MSC4242 state DAG rooms",
affected=event_id,
)
logger.info(
"_compute_event_context_with_maybe_missing_prevs(event_id=%s): Event in room %s is missing prev_events %s: "
"calculating state for a backwards extremity",
@@ -2684,6 +2697,12 @@ class FederationEventHandler:
if supports_msc4242_state_dag(event):
return await self._load_calculated_auth_events(event, context)
return event_auth_events.values()
if supports_msc4242_state_dag(event):
raise AuthError(
code=HTTPStatus.FORBIDDEN,
msg=f"Unknown prev_state_events {missing_auth_event_ids}; fetching them "
"is not implemented for MSC4242 state DAG rooms",
)
if destination is None:
# this shouldn't happen: destination must be set unless we know we have already
# persisted the auth events.