Merge pull request #4814 from matrix-org/erikj/soft_fail_impl

Implement soft fail
pull/4839/head
Erik Johnston 2019-03-08 14:51:06 +00:00 committed by GitHub
commit 39e57f9728
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 1 deletions

1
changelog.d/4814.feature Normal file
View File

@ -0,0 +1 @@
Add checks to incoming events over federation for events evading auth (aka "soft fail").

View File

@ -77,6 +77,20 @@ class _EventInternalMetadata(object):
"""
return getattr(self, "recheck_redaction", False)
def is_soft_failed(self):
"""Whether the event has been soft failed.
Soft failed events should be handled as usual, except:
1. They should not go down sync or event streams, or generally
sent to clients.
2. They should not be added to the forward extremities (and
therefore not to current state).
Returns:
bool
"""
return getattr(self, "soft_failed", False)
def _event_dict_property(key):
# We want to be able to use hasattr with the event dict properties.

View File

@ -45,6 +45,7 @@ from synapse.api.errors import (
SynapseError,
)
from synapse.crypto.event_signing import compute_event_signature
from synapse.event_auth import auth_types_for_event
from synapse.events.validator import EventValidator
from synapse.replication.http.federation import (
ReplicationCleanRoomRestServlet,
@ -1628,6 +1629,7 @@ class FederationHandler(BaseHandler):
origin, event,
state=state,
auth_events=auth_events,
backfilled=backfilled,
)
# reraise does not allow inlineCallbacks to preserve the stacktrace, so we
@ -1672,6 +1674,7 @@ class FederationHandler(BaseHandler):
event,
state=ev_info.get("state"),
auth_events=ev_info.get("auth_events"),
backfilled=backfilled,
)
defer.returnValue(res)
@ -1794,7 +1797,7 @@ class FederationHandler(BaseHandler):
)
@defer.inlineCallbacks
def _prep_event(self, origin, event, state=None, auth_events=None):
def _prep_event(self, origin, event, state, auth_events, backfilled):
"""
Args:
@ -1802,6 +1805,7 @@ class FederationHandler(BaseHandler):
event:
state:
auth_events:
backfilled (bool)
Returns:
Deferred, which resolves to synapse.events.snapshot.EventContext
@ -1843,11 +1847,99 @@ class FederationHandler(BaseHandler):
context.rejected = RejectedReason.AUTH_ERROR
if not context.rejected:
yield self._check_for_soft_fail(event, state, backfilled)
if event.type == EventTypes.GuestAccess and not context.rejected:
yield self.maybe_kick_guest_users(event)
defer.returnValue(context)
@defer.inlineCallbacks
def _check_for_soft_fail(self, event, state, backfilled):
"""Checks if we should soft fail the event, if so marks the event as
such.
Args:
event (FrozenEvent)
state (dict|None): The state at the event if we don't have all the
event's prev events
backfilled (bool): Whether the event is from backfill
Returns:
Deferred
"""
# For new (non-backfilled and non-outlier) events we check if the event
# passes auth based on the current state. If it doesn't then we
# "soft-fail" the event.
do_soft_fail_check = not backfilled and not event.internal_metadata.is_outlier()
if do_soft_fail_check:
extrem_ids = yield self.store.get_latest_event_ids_in_room(
event.room_id,
)
extrem_ids = set(extrem_ids)
prev_event_ids = set(event.prev_event_ids())
if extrem_ids == prev_event_ids:
# If they're the same then the current state is the same as the
# state at the event, so no point rechecking auth for soft fail.
do_soft_fail_check = False
if do_soft_fail_check:
room_version = yield self.store.get_room_version(event.room_id)
# Calculate the "current state".
if state is not None:
# If we're explicitly given the state then we won't have all the
# prev events, and so we have a gap in the graph. In this case
# we want to be a little careful as we might have been down for
# a while and have an incorrect view of the current state,
# however we still want to do checks as gaps are easy to
# maliciously manufacture.
#
# So we use a "current state" that is actually a state
# resolution across the current forward extremities and the
# given state at the event. This should correctly handle cases
# like bans, especially with state res v2.
state_sets = yield self.store.get_state_groups(
event.room_id, extrem_ids,
)
state_sets = list(state_sets.values())
state_sets.append(state)
current_state_ids = yield self.state_handler.resolve_events(
room_version, state_sets, event,
)
current_state_ids = {
k: e.event_id for k, e in iteritems(current_state_ids)
}
else:
current_state_ids = yield self.state_handler.get_current_state_ids(
event.room_id, latest_event_ids=extrem_ids,
)
# Now check if event pass auth against said current state
auth_types = auth_types_for_event(event)
current_state_ids = [
e for k, e in iteritems(current_state_ids)
if k in auth_types
]
current_auth_events = yield self.store.get_events(current_state_ids)
current_auth_events = {
(e.type, e.state_key): e for e in current_auth_events.values()
}
try:
self.auth.check(room_version, event, auth_events=current_auth_events)
except AuthError as e:
logger.warn(
"Failed current state auth resolution for %r because %s",
event, e,
)
event.internal_metadata.soft_failed = True
@defer.inlineCallbacks
def on_query_auth(self, origin, event_id, room_id, remote_auth_chain, rejects,
missing):

View File

@ -537,6 +537,7 @@ class EventsStore(StateGroupWorkerStore, EventFederationStore, EventsWorkerStore
new_events = [
event for event, ctx in event_contexts
if not event.internal_metadata.is_outlier() and not ctx.rejected
and not event.internal_metadata.is_soft_failed()
]
# start with the existing forward extremities

View File

@ -67,6 +67,10 @@ def filter_events_for_client(store, user_id, events, is_peeking=False,
Returns:
Deferred[list[synapse.events.EventBase]]
"""
# Filter out events that have been soft failed so that we don't relay them
# to clients.
events = list(e for e in events if not e.internal_metadata.is_soft_failed())
types = (
(EventTypes.RoomHistoryVisibility, ""),
(EventTypes.Member, user_id),