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

Ratelimit cross user room key share requests. #8957

Merged
merged 18 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8957.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add rate limiters to cross-user key sharing requests.
7 changes: 5 additions & 2 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,14 @@ class EventTypes:

Retention = "m.room.retention"

Presence = "m.presence"

Dummy = "org.matrix.dummy_event"


class EduTypes:
Presence = "m.presence"
RoomKeyRequest = "m.room_key_request"
clokep marked this conversation as resolved.
Show resolved Hide resolved


class RejectedReason:
AUTH_ERROR = "auth_error"

Expand Down
10 changes: 6 additions & 4 deletions synapse/api/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.

from collections import OrderedDict
from typing import Any, Optional, Tuple
from typing import Hashable, Optional, Tuple

from synapse.api.errors import LimitExceededError
from synapse.types import Requester
Expand Down Expand Up @@ -42,7 +42,9 @@ def __init__(self, clock: Clock, rate_hz: float, burst_count: int):
# * How many times an action has occurred since a point in time
# * The point in time
# * The rate_hz of this particular entry. This can vary per request
self.actions = OrderedDict() # type: OrderedDict[Any, Tuple[float, int, float]]
self.actions = (
OrderedDict()
) # type: OrderedDict[Hashable, Tuple[float, int, float]]

def can_requester_do_action(
self,
Expand Down Expand Up @@ -82,7 +84,7 @@ def can_requester_do_action(

def can_do_action(
self,
key: Any,
key: Hashable,
rate_hz: Optional[float] = None,
burst_count: Optional[int] = None,
update: bool = True,
Expand Down Expand Up @@ -175,7 +177,7 @@ def _prune_message_counts(self, time_now_s: int):

def ratelimit(
self,
key: Any,
key: Hashable,
rate_hz: Optional[float] = None,
burst_count: Optional[int] = None,
update: bool = True,
Expand Down
10 changes: 10 additions & 0 deletions synapse/config/ratelimiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ def read_config(self, config, **kwargs):
defaults={"per_second": 0.01, "burst_count": 3},
)

# Ratelimit cross-user key requests:
# * For local requests this is keyed by the sending device.
# * For requests received over federation this is keyed by the origin.
#
# Note that this isn't exposed in the configuration as it is obscure.
self.rc_key_requests = RateLimitConfig(
config.get("rc_key_requests", {}),
defaults={"per_second": 20, "burst_count": 100},
)

self.rc_3pid_validation = RateLimitConfig(
config.get("rc_3pid_validation") or {},
defaults={"per_second": 0.003, "burst_count": 5},
Expand Down
20 changes: 18 additions & 2 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from twisted.internet.abstract import isIPAddress
from twisted.python import failure

from synapse.api.constants import EventTypes, Membership
from synapse.api.constants import EduTypes, EventTypes, Membership
from synapse.api.errors import (
AuthError,
Codes,
Expand All @@ -44,6 +44,7 @@
SynapseError,
UnsupportedRoomVersionError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.events import EventBase
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
Expand Down Expand Up @@ -869,6 +870,13 @@ def __init__(self, hs: "HomeServer"):
# EDU received.
self._edu_type_to_instance = {} # type: Dict[str, List[str]]

# A rate limiter for incoming room key requests per origin.
self._room_key_request_rate_limiter = Ratelimiter(
clock=self.clock,
rate_hz=self.config.rc_key_requests.per_second,
burst_count=self.config.rc_key_requests.burst_count,
)

def register_edu_handler(
self, edu_type: str, handler: Callable[[str, JsonDict], Awaitable[None]]
):
Expand Down Expand Up @@ -917,7 +925,15 @@ def register_instances_for_edu(self, edu_type: str, instance_names: List[str]):
self._edu_type_to_instance[edu_type] = instance_names

async def on_edu(self, edu_type: str, origin: str, content: dict):
if not self.config.use_presence and edu_type == "m.presence":
if not self.config.use_presence and edu_type == EduTypes.Presence:
return

# If the incoming room key requests from a particular origin are over
# the limit, drop them.
if (
edu_type == EduTypes.RoomKeyRequest
and not self._room_key_request_rate_limiter.can_do_action(origin)
):
return

# Check if we have a handler on this instance
Expand Down
24 changes: 22 additions & 2 deletions synapse/handlers/devicemessage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import logging
from typing import TYPE_CHECKING, Any, Dict

from synapse.api.constants import EduTypes
from synapse.api.errors import SynapseError
from synapse.api.ratelimiting import Ratelimiter
from synapse.logging.context import run_in_background
from synapse.logging.opentracing import (
get_active_span_text_map,
Expand All @@ -25,7 +27,7 @@
start_active_span,
)
from synapse.replication.http.devices import ReplicationUserDevicesResyncRestServlet
from synapse.types import JsonDict, UserID, get_domain_from_id
from synapse.types import JsonDict, Requester, UserID, get_domain_from_id
from synapse.util import json_encoder
from synapse.util.stringutils import random_string

Expand Down Expand Up @@ -78,6 +80,12 @@ def __init__(self, hs: "HomeServer"):
ReplicationUserDevicesResyncRestServlet.make_client(hs)
)

self._ratelimiter = Ratelimiter(
clock=hs.get_clock(),
rate_hz=hs.config.rc_key_requests.per_second,
burst_count=hs.config.rc_key_requests.burst_count,
)

async def on_direct_to_device_edu(self, origin: str, content: JsonDict) -> None:
local_messages = {}
sender_user_id = content["sender"]
Expand Down Expand Up @@ -168,15 +176,27 @@ async def _check_for_unknown_devices(

async def send_device_message(
self,
sender_user_id: str,
requester: Requester,
message_type: str,
messages: Dict[str, Dict[str, JsonDict]],
) -> None:
sender_user_id = requester.user.to_string()

set_tag("number_of_messages", len(messages))
set_tag("sender", sender_user_id)
local_messages = {}
remote_messages = {} # type: Dict[str, Dict[str, Dict[str, JsonDict]]]
for user_id, by_device in messages.items():
# Ratelimit local cross-user key requests by the sending device.
if (
message_type == EduTypes.RoomKeyRequest
and user_id != sender_user_id
and self._ratelimiter.can_do_action(
(sender_user_id, requester.device_id)
)
):
continue

# we use UserID.from_string to catch invalid user ids
if self.is_mine(UserID.from_string(user_id)):
messages_by_device = {
Expand Down
4 changes: 2 additions & 2 deletions synapse/handlers/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import random
from typing import TYPE_CHECKING, Iterable, List, Optional

from synapse.api.constants import EventTypes, Membership
from synapse.api.constants import EduTypes, EventTypes, Membership
from synapse.api.errors import AuthError, SynapseError
from synapse.events import EventBase
from synapse.handlers.presence import format_user_presence_state
Expand Down Expand Up @@ -113,7 +113,7 @@ async def get_stream(
states = await presence_handler.get_states(users)
to_add.extend(
{
"type": EventTypes.Presence,
"type": EduTypes.Presence,
"content": format_user_presence_state(state, time_now),
}
for state in states
Expand Down
4 changes: 2 additions & 2 deletions synapse/handlers/initial_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from twisted.internet import defer

from synapse.api.constants import EventTypes, Membership
from synapse.api.constants import EduTypes, EventTypes, Membership
from synapse.api.errors import SynapseError
from synapse.events.validator import EventValidator
from synapse.handlers.presence import format_user_presence_state
Expand Down Expand Up @@ -412,7 +412,7 @@ async def get_presence():

return [
{
"type": EventTypes.Presence,
"type": EduTypes.Presence,
"content": format_user_presence_state(s, time_now),
}
for s in states
Expand Down
4 changes: 1 addition & 3 deletions synapse/rest/client/v2_alpha/sendtodevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,8 @@ async def _put(self, request, message_type, txn_id):
content = parse_json_object_from_request(request)
assert_params_in_dict(content, ("messages",))

sender_user_id = requester.user.to_string()

await self.device_message_handler.send_device_message(
sender_user_id, message_type, content["messages"]
requester, message_type, content["messages"]
)

response = (200, {}) # type: Tuple[int, dict]
Expand Down