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

Commit da957a6

Browse files
authored
Ensure that we correctly auth events returned by send_join (#11012)
This is the final piece of the jigsaw for #9595. As with other changes before this one (eg #10771), we need to make sure that we auth the auth events in the right order, and actually check that their predecessors haven't been rejected. To do this I've reused the existing code we use when persisting outliers elsewhere. I've removed the code for attempting to fetch missing auth_events - the events should have been present in the send_join response, so the likely reason they are missing is that we couldn't verify them, so requesting them again is unlikely to help. Instead, we simply drop any state which relies on those auth events, as we do at a backwards-extremity. See also matrix-org/complement#216 for a test for this.
1 parent 85a09f8 commit da957a6

File tree

2 files changed

+61
-86
lines changed

2 files changed

+61
-86
lines changed

changelog.d/11012.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a long-standing bug which meant that events received over federation were sometimes incorrectly accepted into the room state.

synapse/handlers/federation_event.py

Lines changed: 60 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ async def on_send_membership_event(
361361
# need to.
362362
await self._event_creation_handler.cache_joined_hosts_for_event(event, context)
363363

364+
await self._check_for_soft_fail(event, None, origin=origin)
364365
await self._run_push_actions_and_persist_event(event, context)
365366
return event, context
366367

@@ -402,29 +403,28 @@ async def process_remote_join(
402403
"""Persists the events returned by a send_join
403404
404405
Checks the auth chain is valid (and passes auth checks) for the
405-
state and event. Then persists the auth chain and state atomically.
406-
Persists the event separately. Notifies about the persisted events
407-
where appropriate.
408-
409-
Will attempt to fetch missing auth events.
406+
state and event. Then persists all of the events.
407+
Notifies about the persisted events where appropriate.
410408
411409
Args:
412410
origin: Where the events came from
413-
room_id,
411+
room_id:
414412
auth_events
415413
state
416414
event
417415
room_version: The room version we expect this room to have, and
418416
will raise if it doesn't match the version in the create event.
417+
418+
Returns:
419+
The stream ID after which all events have been persisted.
420+
421+
Raises:
422+
SynapseError if the response is in some way invalid.
419423
"""
420-
events_to_context = {}
421424
for e in itertools.chain(auth_events, state):
422425
e.internal_metadata.outlier = True
423-
events_to_context[e.event_id] = EventContext.for_outlier()
424426

425-
event_map = {
426-
e.event_id: e for e in itertools.chain(auth_events, state, [event])
427-
}
427+
event_map = {e.event_id: e for e in itertools.chain(auth_events, state)}
428428

429429
create_event = None
430430
for e in auth_events:
@@ -444,64 +444,36 @@ async def process_remote_join(
444444
if room_version.identifier != room_version_id:
445445
raise SynapseError(400, "Room version mismatch")
446446

447-
missing_auth_events = set()
448-
for e in itertools.chain(auth_events, state, [event]):
449-
for e_id in e.auth_event_ids():
450-
if e_id not in event_map:
451-
missing_auth_events.add(e_id)
452-
453-
for e_id in missing_auth_events:
454-
m_ev = await self._federation_client.get_pdu(
455-
[origin],
456-
e_id,
457-
room_version=room_version,
458-
outlier=True,
459-
timeout=10000,
460-
)
461-
if m_ev and m_ev.event_id == e_id:
462-
event_map[e_id] = m_ev
463-
else:
464-
logger.info("Failed to find auth event %r", e_id)
465-
466-
for e in itertools.chain(auth_events, state, [event]):
467-
auth_for_e = [
468-
event_map[e_id] for e_id in e.auth_event_ids() if e_id in event_map
469-
]
470-
if create_event:
471-
auth_for_e.append(create_event)
472-
473-
try:
474-
validate_event_for_room_version(room_version, e)
475-
check_auth_rules_for_event(room_version, e, auth_for_e)
476-
except SynapseError as err:
477-
# we may get SynapseErrors here as well as AuthErrors. For
478-
# instance, there are a couple of (ancient) events in some
479-
# rooms whose senders do not have the correct sigil; these
480-
# cause SynapseErrors in auth.check. We don't want to give up
481-
# the attempt to federate altogether in such cases.
482-
483-
logger.warning("Rejecting %s because %s", e.event_id, err.msg)
484-
485-
if e == event:
486-
raise
487-
events_to_context[e.event_id].rejected = RejectedReason.AUTH_ERROR
488-
489-
if auth_events or state:
490-
await self.persist_events_and_notify(
491-
room_id,
492-
[
493-
(e, events_to_context[e.event_id])
494-
for e in itertools.chain(auth_events, state)
495-
],
447+
# filter out any events we have already seen
448+
seen_remotes = await self._store.have_seen_events(room_id, event_map.keys())
449+
for s in seen_remotes:
450+
event_map.pop(s, None)
451+
452+
# persist the auth chain and state events.
453+
#
454+
# any invalid events here will be marked as rejected, and we'll carry on.
455+
#
456+
# any events whose auth events are missing (ie, not in the send_join response,
457+
# and not already in our db) will just be ignored. This is correct behaviour,
458+
# because the reason that auth_events are missing might be due to us being
459+
# unable to validate their signatures. The fact that we can't validate their
460+
# signatures right now doesn't mean that we will *never* be able to, so it
461+
# is premature to reject them.
462+
#
463+
await self._auth_and_persist_outliers(room_id, event_map.values())
464+
465+
# and now persist the join event itself.
466+
logger.info("Peristing join-via-remote %s", event)
467+
with nested_logging_context(suffix=event.event_id):
468+
context = await self._state_handler.compute_event_context(
469+
event, old_state=state
496470
)
497471

498-
new_event_context = await self._state_handler.compute_event_context(
499-
event, old_state=state
500-
)
472+
context = await self._check_event_auth(origin, event, context)
473+
if context.rejected:
474+
raise SynapseError(400, "Join event was rejected")
501475

502-
return await self.persist_events_and_notify(
503-
room_id, [(event, new_event_context)]
504-
)
476+
return await self.persist_events_and_notify(room_id, [(event, context)])
505477

506478
@log_function
507479
async def backfill(
@@ -974,9 +946,15 @@ async def _process_received_pdu(
974946
) -> None:
975947
"""Called when we have a new non-outlier event.
976948
977-
This is called when we have a new event to add to the room DAG - either directly
978-
via a /send request, retrieved via get_missing_events after a /send request, or
979-
backfilled after a client request.
949+
This is called when we have a new event to add to the room DAG. This can be
950+
due to:
951+
* events received directly via a /send request
952+
* events retrieved via get_missing_events after a /send request
953+
* events backfilled after a client request.
954+
955+
It's not currently used for events received from incoming send_{join,knock,leave}
956+
requests (which go via on_send_membership_event), nor for joins created by a
957+
remote join dance (which go via process_remote_join).
980958
981959
We need to do auth checks and put it through the StateHandler.
982960
@@ -1012,11 +990,19 @@ async def _process_received_pdu(
1012990
logger.exception("Unexpected AuthError from _check_event_auth")
1013991
raise FederationError("ERROR", e.code, e.msg, affected=event.event_id)
1014992

993+
if not backfilled and not context.rejected:
994+
# For new (non-backfilled and non-outlier) events we check if the event
995+
# passes auth based on the current state. If it doesn't then we
996+
# "soft-fail" the event.
997+
await self._check_for_soft_fail(event, state, origin=origin)
998+
1015999
await self._run_push_actions_and_persist_event(event, context, backfilled)
10161000

1017-
if backfilled:
1001+
if backfilled or context.rejected:
10181002
return
10191003

1004+
await self._maybe_kick_guest_users(event)
1005+
10201006
# For encrypted messages we check that we know about the sending device,
10211007
# if we don't then we mark the device cache for that user as stale.
10221008
if event.type == EventTypes.Encrypted:
@@ -1317,14 +1303,14 @@ def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
13171303
for auth_event_id in event.auth_event_ids():
13181304
ae = persisted_events.get(auth_event_id)
13191305
if not ae:
1306+
# the fact we can't find the auth event doesn't mean it doesn't
1307+
# exist, which means it is premature to reject `event`. Instead we
1308+
# just ignore it for now.
13201309
logger.warning(
1321-
"Event %s relies on auth_event %s, which could not be found.",
1310+
"Dropping event %s, which relies on auth_event %s, which could not be found",
13221311
event,
13231312
auth_event_id,
13241313
)
1325-
# the fact we can't find the auth event doesn't mean it doesn't
1326-
# exist, which means it is premature to reject `event`. Instead we
1327-
# just ignore it for now.
13281314
return None
13291315
auth.append(ae)
13301316

@@ -1447,10 +1433,6 @@ async def _check_event_auth(
14471433
except AuthError as e:
14481434
logger.warning("Failed auth resolution for %r because %s", event, e)
14491435
context.rejected = RejectedReason.AUTH_ERROR
1450-
return context
1451-
1452-
await self._check_for_soft_fail(event, state, backfilled, origin=origin)
1453-
await self._maybe_kick_guest_users(event)
14541436

14551437
return context
14561438

@@ -1470,7 +1452,6 @@ async def _check_for_soft_fail(
14701452
self,
14711453
event: EventBase,
14721454
state: Optional[Iterable[EventBase]],
1473-
backfilled: bool,
14741455
origin: str,
14751456
) -> None:
14761457
"""Checks if we should soft fail the event; if so, marks the event as
@@ -1479,15 +1460,8 @@ async def _check_for_soft_fail(
14791460
Args:
14801461
event
14811462
state: The state at the event if we don't have all the event's prev events
1482-
backfilled: Whether the event is from backfill
14831463
origin: The host the event originates from.
14841464
"""
1485-
# For new (non-backfilled and non-outlier) events we check if the event
1486-
# passes auth based on the current state. If it doesn't then we
1487-
# "soft-fail" the event.
1488-
if backfilled or event.internal_metadata.is_outlier():
1489-
return
1490-
14911465
extrem_ids_list = await self._store.get_latest_event_ids_in_room(event.room_id)
14921466
extrem_ids = set(extrem_ids_list)
14931467
prev_event_ids = set(event.prev_event_ids())

0 commit comments

Comments
 (0)