mirror of
https://git.anonymousland.org/anonymousland/synapse-product.git
synced 2024-10-01 08:25:44 -04:00
Merge pull request #4814 from matrix-org/erikj/soft_fail_impl
Implement soft fail
This commit is contained in:
commit
39e57f9728
1
changelog.d/4814.feature
Normal file
1
changelog.d/4814.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add checks to incoming events over federation for events evading auth (aka "soft fail").
|
@ -77,6 +77,20 @@ class _EventInternalMetadata(object):
|
|||||||
"""
|
"""
|
||||||
return getattr(self, "recheck_redaction", False)
|
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):
|
def _event_dict_property(key):
|
||||||
# We want to be able to use hasattr with the event dict properties.
|
# We want to be able to use hasattr with the event dict properties.
|
||||||
|
@ -45,6 +45,7 @@ from synapse.api.errors import (
|
|||||||
SynapseError,
|
SynapseError,
|
||||||
)
|
)
|
||||||
from synapse.crypto.event_signing import compute_event_signature
|
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.events.validator import EventValidator
|
||||||
from synapse.replication.http.federation import (
|
from synapse.replication.http.federation import (
|
||||||
ReplicationCleanRoomRestServlet,
|
ReplicationCleanRoomRestServlet,
|
||||||
@ -1628,6 +1629,7 @@ class FederationHandler(BaseHandler):
|
|||||||
origin, event,
|
origin, event,
|
||||||
state=state,
|
state=state,
|
||||||
auth_events=auth_events,
|
auth_events=auth_events,
|
||||||
|
backfilled=backfilled,
|
||||||
)
|
)
|
||||||
|
|
||||||
# reraise does not allow inlineCallbacks to preserve the stacktrace, so we
|
# reraise does not allow inlineCallbacks to preserve the stacktrace, so we
|
||||||
@ -1672,6 +1674,7 @@ class FederationHandler(BaseHandler):
|
|||||||
event,
|
event,
|
||||||
state=ev_info.get("state"),
|
state=ev_info.get("state"),
|
||||||
auth_events=ev_info.get("auth_events"),
|
auth_events=ev_info.get("auth_events"),
|
||||||
|
backfilled=backfilled,
|
||||||
)
|
)
|
||||||
defer.returnValue(res)
|
defer.returnValue(res)
|
||||||
|
|
||||||
@ -1794,7 +1797,7 @@ class FederationHandler(BaseHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _prep_event(self, origin, event, state=None, auth_events=None):
|
def _prep_event(self, origin, event, state, auth_events, backfilled):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1802,6 +1805,7 @@ class FederationHandler(BaseHandler):
|
|||||||
event:
|
event:
|
||||||
state:
|
state:
|
||||||
auth_events:
|
auth_events:
|
||||||
|
backfilled (bool)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred, which resolves to synapse.events.snapshot.EventContext
|
Deferred, which resolves to synapse.events.snapshot.EventContext
|
||||||
@ -1843,11 +1847,99 @@ class FederationHandler(BaseHandler):
|
|||||||
|
|
||||||
context.rejected = RejectedReason.AUTH_ERROR
|
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:
|
if event.type == EventTypes.GuestAccess and not context.rejected:
|
||||||
yield self.maybe_kick_guest_users(event)
|
yield self.maybe_kick_guest_users(event)
|
||||||
|
|
||||||
defer.returnValue(context)
|
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
|
@defer.inlineCallbacks
|
||||||
def on_query_auth(self, origin, event_id, room_id, remote_auth_chain, rejects,
|
def on_query_auth(self, origin, event_id, room_id, remote_auth_chain, rejects,
|
||||||
missing):
|
missing):
|
||||||
|
@ -537,6 +537,7 @@ class EventsStore(StateGroupWorkerStore, EventFederationStore, EventsWorkerStore
|
|||||||
new_events = [
|
new_events = [
|
||||||
event for event, ctx in event_contexts
|
event for event, ctx in event_contexts
|
||||||
if not event.internal_metadata.is_outlier() and not ctx.rejected
|
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
|
# start with the existing forward extremities
|
||||||
|
@ -67,6 +67,10 @@ def filter_events_for_client(store, user_id, events, is_peeking=False,
|
|||||||
Returns:
|
Returns:
|
||||||
Deferred[list[synapse.events.EventBase]]
|
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 = (
|
types = (
|
||||||
(EventTypes.RoomHistoryVisibility, ""),
|
(EventTypes.RoomHistoryVisibility, ""),
|
||||||
(EventTypes.Member, user_id),
|
(EventTypes.Member, user_id),
|
||||||
|
Loading…
Reference in New Issue
Block a user