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

Commit cc33d9e

Browse files
authored
Check auth on received events' auth_events (#11001)
Currently, when we receive an event whose auth_events differ from those we expect, we state-resolve between the two state sets, and check that the event passes auth based on the resolved state. This means that it's possible for us to accept events which don't pass auth at their declared auth_events (or where the auth events themselves were rejected), leading to problems down the line like #10083. This change means we will: * ignore any events where we cannot find the auth events * reject any events whose auth events were rejected * reject any events which do not pass auth at their declared auth_events. Together with a whole raft of previous work, this is a partial fix to #9595. Fixes #6643. Based on #11009.
1 parent a5d2ea3 commit cc33d9e

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

changelog.d/11001.bugfix

+1
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

+97-2
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,10 @@ async def _check_event_auth(
12561256
12571257
Returns:
12581258
The updated context object.
1259+
1260+
Raises:
1261+
AuthError if we were unable to find copies of the event's auth events.
1262+
(Most other failures just cause us to set `context.rejected`.)
12591263
"""
12601264
# This method should only be used for non-outliers
12611265
assert not event.internal_metadata.outlier
@@ -1272,7 +1276,26 @@ async def _check_event_auth(
12721276
context.rejected = RejectedReason.AUTH_ERROR
12731277
return context
12741278

1275-
# calculate what the auth events *should* be, to use as a basis for auth.
1279+
# next, check that we have all of the event's auth events.
1280+
#
1281+
# Note that this can raise AuthError, which we want to propagate to the
1282+
# caller rather than swallow with `context.rejected` (since we cannot be
1283+
# certain that there is a permanent problem with the event).
1284+
claimed_auth_events = await self._load_or_fetch_auth_events_for_event(
1285+
origin, event
1286+
)
1287+
1288+
# ... and check that the event passes auth at those auth events.
1289+
try:
1290+
check_auth_rules_for_event(room_version_obj, event, claimed_auth_events)
1291+
except AuthError as e:
1292+
logger.warning(
1293+
"While checking auth of %r against auth_events: %s", event, e
1294+
)
1295+
context.rejected = RejectedReason.AUTH_ERROR
1296+
return context
1297+
1298+
# now check auth against what we think the auth events *should* be.
12761299
prev_state_ids = await context.get_prev_state_ids()
12771300
auth_events_ids = self._event_auth_handler.compute_auth_events(
12781301
event, prev_state_ids, for_verification=True
@@ -1472,6 +1495,9 @@ async def _update_auth_events_and_context_for_auth(
14721495
# if we have missing events, we need to fetch those events from somewhere.
14731496
#
14741497
# we start by checking if they are in the store, and then try calling /event_auth/.
1498+
#
1499+
# TODO: this code is now redundant, since it should be impossible for us to
1500+
# get here without already having the auth events.
14751501
if missing_auth:
14761502
have_events = await self._store.have_seen_events(
14771503
event.room_id, missing_auth
@@ -1575,7 +1601,7 @@ async def _update_auth_events_and_context_for_auth(
15751601
logger.info(
15761602
"After state res: updating auth_events with new state %s",
15771603
{
1578-
(d.type, d.state_key): d.event_id
1604+
d
15791605
for d in new_state.values()
15801606
if auth_events.get((d.type, d.state_key)) != d
15811607
},
@@ -1589,6 +1615,75 @@ async def _update_auth_events_and_context_for_auth(
15891615

15901616
return context, auth_events
15911617

1618+
async def _load_or_fetch_auth_events_for_event(
1619+
self, destination: str, event: EventBase
1620+
) -> Collection[EventBase]:
1621+
"""Fetch this event's auth_events, from database or remote
1622+
1623+
Loads any of the auth_events that we already have from the database/cache. If
1624+
there are any that are missing, calls /event_auth to get the complete auth
1625+
chain for the event (and then attempts to load the auth_events again).
1626+
1627+
If any of the auth_events cannot be found, raises an AuthError. This can happen
1628+
for a number of reasons; eg: the events don't exist, or we were unable to talk
1629+
to `destination`, or we couldn't validate the signature on the event (which
1630+
in turn has multiple potential causes).
1631+
1632+
Args:
1633+
destination: where to send the /event_auth request. Typically the server
1634+
that sent us `event` in the first place.
1635+
event: the event whose auth_events we want
1636+
1637+
Returns:
1638+
all of the events in `event.auth_events`, after deduplication
1639+
1640+
Raises:
1641+
AuthError if we were unable to fetch the auth_events for any reason.
1642+
"""
1643+
event_auth_event_ids = set(event.auth_event_ids())
1644+
event_auth_events = await self._store.get_events(
1645+
event_auth_event_ids, allow_rejected=True
1646+
)
1647+
missing_auth_event_ids = event_auth_event_ids.difference(
1648+
event_auth_events.keys()
1649+
)
1650+
if not missing_auth_event_ids:
1651+
return event_auth_events.values()
1652+
1653+
logger.info(
1654+
"Event %s refers to unknown auth events %s: fetching auth chain",
1655+
event,
1656+
missing_auth_event_ids,
1657+
)
1658+
try:
1659+
await self._get_remote_auth_chain_for_event(
1660+
destination, event.room_id, event.event_id
1661+
)
1662+
except Exception as e:
1663+
logger.warning("Failed to get auth chain for %s: %s", event, e)
1664+
# in this case, it's very likely we still won't have all the auth
1665+
# events - but we pick that up below.
1666+
1667+
# try to fetch the auth events we missed list time.
1668+
extra_auth_events = await self._store.get_events(
1669+
missing_auth_event_ids, allow_rejected=True
1670+
)
1671+
missing_auth_event_ids.difference_update(extra_auth_events.keys())
1672+
event_auth_events.update(extra_auth_events)
1673+
if not missing_auth_event_ids:
1674+
return event_auth_events.values()
1675+
1676+
# we still don't have all the auth events.
1677+
logger.warning(
1678+
"Missing auth events for %s: %s",
1679+
event,
1680+
shortstr(missing_auth_event_ids),
1681+
)
1682+
# the fact we can't find the auth event doesn't mean it doesn't
1683+
# exist, which means it is premature to store `event` as rejected.
1684+
# instead we raise an AuthError, which will make the caller ignore it.
1685+
raise AuthError(code=HTTPStatus.FORBIDDEN, msg="Auth events could not be found")
1686+
15921687
async def _get_remote_auth_chain_for_event(
15931688
self, destination: str, room_id: str, event_id: str
15941689
) -> None:

0 commit comments

Comments
 (0)