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

Commit

Permalink
Check *all* auth events for room id and rejection (#11009)
Browse files Browse the repository at this point in the history
This fixes a bug where we would accept an event whose `auth_events` include
rejected events, if the rejected event was shadowed by another `auth_event`
with same `(type, state_key)`.

The approach is to pass a list of auth events into
`check_auth_rules_for_event` instead of a dict, which of course means updating
the call sites.

This is an extension of #10956.
  • Loading branch information
richvdh authored Oct 18, 2021
1 parent 73743b8 commit a5d2ea3
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 85 deletions.
1 change: 1 addition & 0 deletions changelog.d/11009.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug which meant that events received over federation were sometimes incorrectly accepted into the room state.
33 changes: 15 additions & 18 deletions synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.

import logging
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union

from canonicaljson import encode_canonical_json
from signedjson.key import decode_verify_key_bytes
Expand Down Expand Up @@ -113,7 +113,7 @@ def validate_event_for_room_version(


def check_auth_rules_for_event(
room_version_obj: RoomVersion, event: EventBase, auth_events: StateMap[EventBase]
room_version_obj: RoomVersion, event: EventBase, auth_events: Iterable[EventBase]
) -> None:
"""Check that an event complies with the auth rules
Expand All @@ -137,8 +137,6 @@ def check_auth_rules_for_event(
Raises:
AuthError if the checks fail
"""
assert isinstance(auth_events, dict)

# We need to ensure that the auth events are actually for the same room, to
# stop people from using powers they've been granted in other rooms for
# example.
Expand All @@ -147,7 +145,7 @@ def check_auth_rules_for_event(
# the state res algorithm isn't silly enough to give us events from different rooms.
# Still, it's easier to do it anyway.
room_id = event.room_id
for auth_event in auth_events.values():
for auth_event in auth_events:
if auth_event.room_id != room_id:
raise AuthError(
403,
Expand Down Expand Up @@ -186,16 +184,18 @@ def check_auth_rules_for_event(
logger.debug("Allowing! %s", event)
return

auth_dict = {(e.type, e.state_key): e for e in auth_events}

# 3. If event does not have a m.room.create in its auth_events, reject.
creation_event = auth_events.get((EventTypes.Create, ""), None)
creation_event = auth_dict.get((EventTypes.Create, ""), None)
if not creation_event:
raise AuthError(403, "No create event in auth events")

# additional check for m.federate
creating_domain = get_domain_from_id(event.room_id)
originating_domain = get_domain_from_id(event.sender)
if creating_domain != originating_domain:
if not _can_federate(event, auth_events):
if not _can_federate(event, auth_dict):
raise AuthError(403, "This room has been marked as unfederatable.")

# 4. If type is m.room.aliases
Expand All @@ -217,44 +217,41 @@ def check_auth_rules_for_event(
logger.debug("Allowing! %s", event)
return

if logger.isEnabledFor(logging.DEBUG):
logger.debug("Auth events: %s", [a.event_id for a in auth_events.values()])

# 5. If type is m.room.membership
if event.type == EventTypes.Member:
_is_membership_change_allowed(room_version_obj, event, auth_events)
_is_membership_change_allowed(room_version_obj, event, auth_dict)
logger.debug("Allowing! %s", event)
return

_check_event_sender_in_room(event, auth_events)
_check_event_sender_in_room(event, auth_dict)

# Special case to allow m.room.third_party_invite events wherever
# a user is allowed to issue invites. Fixes
# https://github.com/vector-im/vector-web/issues/1208 hopefully
if event.type == EventTypes.ThirdPartyInvite:
user_level = get_user_power_level(event.user_id, auth_events)
invite_level = get_named_level(auth_events, "invite", 0)
user_level = get_user_power_level(event.user_id, auth_dict)
invite_level = get_named_level(auth_dict, "invite", 0)

if user_level < invite_level:
raise AuthError(403, "You don't have permission to invite users")
else:
logger.debug("Allowing! %s", event)
return

_can_send_event(event, auth_events)
_can_send_event(event, auth_dict)

if event.type == EventTypes.PowerLevels:
_check_power_levels(room_version_obj, event, auth_events)
_check_power_levels(room_version_obj, event, auth_dict)

if event.type == EventTypes.Redaction:
check_redaction(room_version_obj, event, auth_events)
check_redaction(room_version_obj, event, auth_dict)

if (
event.type == EventTypes.MSC2716_INSERTION
or event.type == EventTypes.MSC2716_BATCH
or event.type == EventTypes.MSC2716_MARKER
):
check_historical(room_version_obj, event, auth_events)
check_historical(room_version_obj, event, auth_dict)

logger.debug("Allowing! %s", event)

Expand Down
3 changes: 1 addition & 2 deletions synapse/handlers/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ async def check_auth_rules_from_context(
"""Check an event passes the auth rules at its own auth events"""
auth_event_ids = event.auth_event_ids()
auth_events_by_id = await self._store.get_events(auth_event_ids)
auth_events = {(e.type, e.state_key): e for e in auth_events_by_id.values()}
check_auth_rules_for_event(room_version_obj, event, auth_events)
check_auth_rules_for_event(room_version_obj, event, auth_events_by_id.values())

def compute_auth_events(
self,
Expand Down
10 changes: 4 additions & 6 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,13 +1167,11 @@ async def _persist_auth_tree(
logger.info("Failed to find auth event %r", e_id)

for e in itertools.chain(auth_events, state, [event]):
auth_for_e = {
(event_map[e_id].type, event_map[e_id].state_key): event_map[e_id]
for e_id in e.auth_event_ids()
if e_id in event_map
}
auth_for_e = [
event_map[e_id] for e_id in e.auth_event_ids() if e_id in event_map
]
if create_event:
auth_for_e[(EventTypes.Create, "")] = create_event
auth_for_e.append(create_event)

try:
validate_event_for_room_version(room_version, e)
Expand Down
16 changes: 8 additions & 8 deletions synapse/handlers/federation_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ async def _auth_and_persist_fetched_events_inner(

def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
with nested_logging_context(suffix=event.event_id):
auth = {}
auth = []
for auth_event_id in event.auth_event_ids():
ae = persisted_events.get(auth_event_id)
if not ae:
Expand All @@ -1216,7 +1216,7 @@ def prep(event: EventBase) -> Optional[Tuple[EventBase, EventContext]]:
# exist, which means it is premature to reject `event`. Instead we
# just ignore it for now.
return None
auth[(ae.type, ae.state_key)] = ae
auth.append(ae)

context = EventContext.for_outlier()
try:
Expand Down Expand Up @@ -1305,7 +1305,9 @@ async def _check_event_auth(
auth_events_for_auth = calculated_auth_event_map

try:
check_auth_rules_for_event(room_version_obj, event, auth_events_for_auth)
check_auth_rules_for_event(
room_version_obj, event, auth_events_for_auth.values()
)
except AuthError as e:
logger.warning("Failed auth resolution for %r because %s", event, e)
context.rejected = RejectedReason.AUTH_ERROR
Expand Down Expand Up @@ -1403,11 +1405,9 @@ async def _check_for_soft_fail(
current_state_ids_list = [
e for k, e in current_state_ids.items() if k in auth_types
]

auth_events_map = await self._store.get_events(current_state_ids_list)
current_auth_events = {
(e.type, e.state_key): e for e in auth_events_map.values()
}
current_auth_events = await self._store.get_events_as_list(
current_state_ids_list
)

try:
check_auth_rules_for_event(room_version_obj, event, current_auth_events)
Expand Down
4 changes: 2 additions & 2 deletions synapse/state/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ def _resolve_auth_events(
event_auth.check_auth_rules_for_event(
RoomVersions.V1,
event,
auth_events,
auth_events.values(),
)
prev_event = event
except AuthError:
Expand All @@ -350,7 +350,7 @@ def _resolve_normal_events(
event_auth.check_auth_rules_for_event(
RoomVersions.V1,
event,
auth_events,
auth_events.values(),
)
return event
except AuthError:
Expand Down
2 changes: 1 addition & 1 deletion synapse/state/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ async def _iterative_auth_checks(
event_auth.check_auth_rules_for_event(
room_version,
event,
auth_events,
auth_events.values(),
)

resolved_state[(event.type, event.state_key)] = event_id
Expand Down
Loading

0 comments on commit a5d2ea3

Please sign in to comment.