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

Commit fe15a86

Browse files
authored
Rip out auth-event reconciliation code (#12943)
There is a corner in `_check_event_auth` (long known as "the weird corner") where, if we get an event with auth_events which don't match those we were expecting, we attempt to resolve the diffence between our state and the remote's with a state resolution. This isn't specced, and there's general agreement we shouldn't be doing it. However, it turns out that the faster-joins code was relying on it, so we need to introduce something similar (but rather simpler) for that.
1 parent df55b37 commit fe15a86

File tree

6 files changed

+88
-375
lines changed

6 files changed

+88
-375
lines changed

changelog.d/12943.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove code which incorrectly attempted to reconcile state with remote servers when processing incoming events.

synapse/handlers/federation_event.py

Lines changed: 82 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import collections
1516
import itertools
1617
import logging
1718
from http import HTTPStatus
@@ -347,7 +348,7 @@ async def on_send_membership_event(
347348
event.internal_metadata.send_on_behalf_of = origin
348349

349350
context = await self._state_handler.compute_event_context(event)
350-
context = await self._check_event_auth(origin, event, context)
351+
await self._check_event_auth(origin, event, context)
351352
if context.rejected:
352353
raise SynapseError(
353354
403, f"{event.membership} event was rejected", Codes.FORBIDDEN
@@ -485,7 +486,7 @@ async def process_remote_join(
485486
partial_state=partial_state,
486487
)
487488

488-
context = await self._check_event_auth(origin, event, context)
489+
await self._check_event_auth(origin, event, context)
489490
if context.rejected:
490491
raise SynapseError(400, "Join event was rejected")
491492

@@ -1116,11 +1117,7 @@ async def _process_received_pdu(
11161117
state_ids_before_event=state_ids,
11171118
)
11181119
try:
1119-
context = await self._check_event_auth(
1120-
origin,
1121-
event,
1122-
context,
1123-
)
1120+
await self._check_event_auth(origin, event, context)
11241121
except AuthError as e:
11251122
# This happens only if we couldn't find the auth events. We'll already have
11261123
# logged a warning, so now we just convert to a FederationError.
@@ -1495,11 +1492,8 @@ async def prep(event: EventBase) -> None:
14951492
)
14961493

14971494
async def _check_event_auth(
1498-
self,
1499-
origin: str,
1500-
event: EventBase,
1501-
context: EventContext,
1502-
) -> EventContext:
1495+
self, origin: str, event: EventBase, context: EventContext
1496+
) -> None:
15031497
"""
15041498
Checks whether an event should be rejected (for failing auth checks).
15051499
@@ -1509,9 +1503,6 @@ async def _check_event_auth(
15091503
context:
15101504
The event context.
15111505
1512-
Returns:
1513-
The updated context object.
1514-
15151506
Raises:
15161507
AuthError if we were unable to find copies of the event's auth events.
15171508
(Most other failures just cause us to set `context.rejected`.)
@@ -1526,7 +1517,7 @@ async def _check_event_auth(
15261517
logger.warning("While validating received event %r: %s", event, e)
15271518
# TODO: use a different rejected reason here?
15281519
context.rejected = RejectedReason.AUTH_ERROR
1529-
return context
1520+
return
15301521

15311522
# next, check that we have all of the event's auth events.
15321523
#
@@ -1538,6 +1529,9 @@ async def _check_event_auth(
15381529
)
15391530

15401531
# ... and check that the event passes auth at those auth events.
1532+
# https://spec.matrix.org/v1.3/server-server-api/#checks-performed-on-receipt-of-a-pdu:
1533+
# 4. Passes authorization rules based on the event’s auth events,
1534+
# otherwise it is rejected.
15411535
try:
15421536
await check_state_independent_auth_rules(self._store, event)
15431537
check_state_dependent_auth_rules(event, claimed_auth_events)
@@ -1546,55 +1540,90 @@ async def _check_event_auth(
15461540
"While checking auth of %r against auth_events: %s", event, e
15471541
)
15481542
context.rejected = RejectedReason.AUTH_ERROR
1549-
return context
1543+
return
1544+
1545+
# now check the auth rules pass against the room state before the event
1546+
# https://spec.matrix.org/v1.3/server-server-api/#checks-performed-on-receipt-of-a-pdu:
1547+
# 5. Passes authorization rules based on the state before the event,
1548+
# otherwise it is rejected.
1549+
#
1550+
# ... however, if we only have partial state for the room, then there is a good
1551+
# chance that we'll be missing some of the state needed to auth the new event.
1552+
# So, we state-resolve the auth events that we are given against the state that
1553+
# we know about, which ensures things like bans are applied. (Note that we'll
1554+
# already have checked we have all the auth events, in
1555+
# _load_or_fetch_auth_events_for_event above)
1556+
if context.partial_state:
1557+
room_version = await self._store.get_room_version_id(event.room_id)
1558+
1559+
local_state_id_map = await context.get_prev_state_ids()
1560+
claimed_auth_events_id_map = {
1561+
(ev.type, ev.state_key): ev.event_id for ev in claimed_auth_events
1562+
}
1563+
1564+
state_for_auth_id_map = (
1565+
await self._state_resolution_handler.resolve_events_with_store(
1566+
event.room_id,
1567+
room_version,
1568+
[local_state_id_map, claimed_auth_events_id_map],
1569+
event_map=None,
1570+
state_res_store=StateResolutionStore(self._store),
1571+
)
1572+
)
1573+
else:
1574+
event_types = event_auth.auth_types_for_event(event.room_version, event)
1575+
state_for_auth_id_map = await context.get_prev_state_ids(
1576+
StateFilter.from_types(event_types)
1577+
)
15501578

1551-
# now check auth against what we think the auth events *should* be.
1552-
event_types = event_auth.auth_types_for_event(event.room_version, event)
1553-
prev_state_ids = await context.get_prev_state_ids(
1554-
StateFilter.from_types(event_types)
1579+
calculated_auth_event_ids = self._event_auth_handler.compute_auth_events(
1580+
event, state_for_auth_id_map, for_verification=True
15551581
)
15561582

1557-
auth_events_ids = self._event_auth_handler.compute_auth_events(
1558-
event, prev_state_ids, for_verification=True
1583+
# if those are the same, we're done here.
1584+
if collections.Counter(event.auth_event_ids()) == collections.Counter(
1585+
calculated_auth_event_ids
1586+
):
1587+
return
1588+
1589+
# otherwise, re-run the auth checks based on what we calculated.
1590+
calculated_auth_events = await self._store.get_events_as_list(
1591+
calculated_auth_event_ids
15591592
)
1560-
auth_events_x = await self._store.get_events(auth_events_ids)
1593+
1594+
# log the differences
1595+
1596+
claimed_auth_event_map = {(e.type, e.state_key): e for e in claimed_auth_events}
15611597
calculated_auth_event_map = {
1562-
(e.type, e.state_key): e for e in auth_events_x.values()
1598+
(e.type, e.state_key): e for e in calculated_auth_events
15631599
}
1600+
logger.info(
1601+
"event's auth_events are different to our calculated auth_events. "
1602+
"Claimed but not calculated: %s. Calculated but not claimed: %s",
1603+
[
1604+
ev
1605+
for k, ev in claimed_auth_event_map.items()
1606+
if k not in calculated_auth_event_map
1607+
or calculated_auth_event_map[k].event_id != ev.event_id
1608+
],
1609+
[
1610+
ev
1611+
for k, ev in calculated_auth_event_map.items()
1612+
if k not in claimed_auth_event_map
1613+
or claimed_auth_event_map[k].event_id != ev.event_id
1614+
],
1615+
)
15641616

15651617
try:
1566-
updated_auth_events = await self._update_auth_events_for_auth(
1618+
check_state_dependent_auth_rules(event, calculated_auth_events)
1619+
except AuthError as e:
1620+
logger.warning(
1621+
"While checking auth of %r against room state before the event: %s",
15671622
event,
1568-
calculated_auth_event_map=calculated_auth_event_map,
1569-
)
1570-
except Exception:
1571-
# We don't really mind if the above fails, so lets not fail
1572-
# processing if it does. However, it really shouldn't fail so
1573-
# let's still log as an exception since we'll still want to fix
1574-
# any bugs.
1575-
logger.exception(
1576-
"Failed to double check auth events for %s with remote. "
1577-
"Ignoring failure and continuing processing of event.",
1578-
event.event_id,
1579-
)
1580-
updated_auth_events = None
1581-
1582-
if updated_auth_events:
1583-
context = await self._update_context_for_auth_events(
1584-
event, context, updated_auth_events
1623+
e,
15851624
)
1586-
auth_events_for_auth = updated_auth_events
1587-
else:
1588-
auth_events_for_auth = calculated_auth_event_map
1589-
1590-
try:
1591-
check_state_dependent_auth_rules(event, auth_events_for_auth.values())
1592-
except AuthError as e:
1593-
logger.warning("Failed auth resolution for %r because %s", event, e)
15941625
context.rejected = RejectedReason.AUTH_ERROR
15951626

1596-
return context
1597-
15981627
async def _maybe_kick_guest_users(self, event: EventBase) -> None:
15991628
if event.type != EventTypes.GuestAccess:
16001629
return
@@ -1704,93 +1733,6 @@ async def _check_for_soft_fail(
17041733
soft_failed_event_counter.inc()
17051734
event.internal_metadata.soft_failed = True
17061735

1707-
async def _update_auth_events_for_auth(
1708-
self,
1709-
event: EventBase,
1710-
calculated_auth_event_map: StateMap[EventBase],
1711-
) -> Optional[StateMap[EventBase]]:
1712-
"""Helper for _check_event_auth. See there for docs.
1713-
1714-
Checks whether a given event has the expected auth events. If it
1715-
doesn't then we talk to the remote server to compare state to see if
1716-
we can come to a consensus (e.g. if one server missed some valid
1717-
state).
1718-
1719-
This attempts to resolve any potential divergence of state between
1720-
servers, but is not essential and so failures should not block further
1721-
processing of the event.
1722-
1723-
Args:
1724-
event:
1725-
1726-
calculated_auth_event_map:
1727-
Our calculated auth_events based on the state of the room
1728-
at the event's position in the DAG.
1729-
1730-
Returns:
1731-
updated auth event map, or None if no changes are needed.
1732-
1733-
"""
1734-
assert not event.internal_metadata.outlier
1735-
1736-
# check for events which are in the event's claimed auth_events, but not
1737-
# in our calculated event map.
1738-
event_auth_events = set(event.auth_event_ids())
1739-
different_auth = event_auth_events.difference(
1740-
e.event_id for e in calculated_auth_event_map.values()
1741-
)
1742-
1743-
if not different_auth:
1744-
return None
1745-
1746-
logger.info(
1747-
"auth_events refers to events which are not in our calculated auth "
1748-
"chain: %s",
1749-
different_auth,
1750-
)
1751-
1752-
# XXX: currently this checks for redactions but I'm not convinced that is
1753-
# necessary?
1754-
different_events = await self._store.get_events_as_list(different_auth)
1755-
1756-
# double-check they're all in the same room - we should already have checked
1757-
# this but it doesn't hurt to check again.
1758-
for d in different_events:
1759-
assert (
1760-
d.room_id == event.room_id
1761-
), f"Event {event.event_id} refers to auth_event {d.event_id} which is in a different room"
1762-
1763-
# now we state-resolve between our own idea of the auth events, and the remote's
1764-
# idea of them.
1765-
1766-
local_state = calculated_auth_event_map.values()
1767-
remote_auth_events = dict(calculated_auth_event_map)
1768-
remote_auth_events.update({(d.type, d.state_key): d for d in different_events})
1769-
remote_state = remote_auth_events.values()
1770-
1771-
room_version = await self._store.get_room_version_id(event.room_id)
1772-
new_state = await self._state_handler.resolve_events(
1773-
room_version, (local_state, remote_state), event
1774-
)
1775-
different_state = {
1776-
(d.type, d.state_key): d
1777-
for d in new_state.values()
1778-
if calculated_auth_event_map.get((d.type, d.state_key)) != d
1779-
}
1780-
if not different_state:
1781-
logger.info("State res returned no new state")
1782-
return None
1783-
1784-
logger.info(
1785-
"After state res: updating auth_events with new state %s",
1786-
different_state.values(),
1787-
)
1788-
1789-
# take a copy of calculated_auth_event_map before we modify it.
1790-
auth_events = dict(calculated_auth_event_map)
1791-
auth_events.update(different_state)
1792-
return auth_events
1793-
17941736
async def _load_or_fetch_auth_events_for_event(
17951737
self, destination: str, event: EventBase
17961738
) -> Collection[EventBase]:
@@ -1888,61 +1830,6 @@ async def _get_remote_auth_chain_for_event(
18881830

18891831
await self._auth_and_persist_outliers(room_id, remote_auth_events)
18901832

1891-
async def _update_context_for_auth_events(
1892-
self, event: EventBase, context: EventContext, auth_events: StateMap[EventBase]
1893-
) -> EventContext:
1894-
"""Update the state_ids in an event context after auth event resolution,
1895-
storing the changes as a new state group.
1896-
1897-
Args:
1898-
event: The event we're handling the context for
1899-
1900-
context: initial event context
1901-
1902-
auth_events: Events to update in the event context.
1903-
1904-
Returns:
1905-
new event context
1906-
"""
1907-
# exclude the state key of the new event from the current_state in the context.
1908-
if event.is_state():
1909-
event_key: Optional[Tuple[str, str]] = (event.type, event.state_key)
1910-
else:
1911-
event_key = None
1912-
state_updates = {
1913-
k: a.event_id for k, a in auth_events.items() if k != event_key
1914-
}
1915-
1916-
current_state_ids = await context.get_current_state_ids()
1917-
current_state_ids = dict(current_state_ids) # type: ignore
1918-
1919-
current_state_ids.update(state_updates)
1920-
1921-
prev_state_ids = await context.get_prev_state_ids()
1922-
prev_state_ids = dict(prev_state_ids)
1923-
1924-
prev_state_ids.update({k: a.event_id for k, a in auth_events.items()})
1925-
1926-
# create a new state group as a delta from the existing one.
1927-
prev_group = context.state_group
1928-
state_group = await self._state_storage_controller.store_state_group(
1929-
event.event_id,
1930-
event.room_id,
1931-
prev_group=prev_group,
1932-
delta_ids=state_updates,
1933-
current_state_ids=current_state_ids,
1934-
)
1935-
1936-
return EventContext.with_state(
1937-
storage=self._storage_controllers,
1938-
state_group=state_group,
1939-
state_group_before_event=context.state_group_before_event,
1940-
state_delta_due_to_event=state_updates,
1941-
prev_group=prev_group,
1942-
delta_ids=state_updates,
1943-
partial_state=context.partial_state,
1944-
)
1945-
19461833
async def _run_push_actions_and_persist_event(
19471834
self, event: EventBase, context: EventContext, backfilled: bool = False
19481835
) -> None:

0 commit comments

Comments
 (0)