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

Commit fc8b3d8

Browse files
authored
Ratelimit cross-user key sharing requests. (#8957)
1 parent 179c095 commit fc8b3d8

File tree

9 files changed

+67
-17
lines changed

9 files changed

+67
-17
lines changed

changelog.d/8957.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add rate limiters to cross-user key sharing requests.

synapse/api/constants.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,14 @@ class EventTypes:
9898

9999
Retention = "m.room.retention"
100100

101-
Presence = "m.presence"
102-
103101
Dummy = "org.matrix.dummy_event"
104102

105103

104+
class EduTypes:
105+
Presence = "m.presence"
106+
RoomKeyRequest = "m.room_key_request"
107+
108+
106109
class RejectedReason:
107110
AUTH_ERROR = "auth_error"
108111

synapse/api/ratelimiting.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515

1616
from collections import OrderedDict
17-
from typing import Any, Optional, Tuple
17+
from typing import Hashable, Optional, Tuple
1818

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

4749
def can_requester_do_action(
4850
self,
@@ -82,7 +84,7 @@ def can_requester_do_action(
8284

8385
def can_do_action(
8486
self,
85-
key: Any,
87+
key: Hashable,
8688
rate_hz: Optional[float] = None,
8789
burst_count: Optional[int] = None,
8890
update: bool = True,
@@ -175,7 +177,7 @@ def _prune_message_counts(self, time_now_s: int):
175177

176178
def ratelimit(
177179
self,
178-
key: Any,
180+
key: Hashable,
179181
rate_hz: Optional[float] = None,
180182
burst_count: Optional[int] = None,
181183
update: bool = True,

synapse/config/ratelimiting.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ def read_config(self, config, **kwargs):
102102
defaults={"per_second": 0.01, "burst_count": 3},
103103
)
104104

105+
# Ratelimit cross-user key requests:
106+
# * For local requests this is keyed by the sending device.
107+
# * For requests received over federation this is keyed by the origin.
108+
#
109+
# Note that this isn't exposed in the configuration as it is obscure.
110+
self.rc_key_requests = RateLimitConfig(
111+
config.get("rc_key_requests", {}),
112+
defaults={"per_second": 20, "burst_count": 100},
113+
)
114+
105115
self.rc_3pid_validation = RateLimitConfig(
106116
config.get("rc_3pid_validation") or {},
107117
defaults={"per_second": 0.003, "burst_count": 5},

synapse/federation/federation_server.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from twisted.internet.abstract import isIPAddress
3535
from twisted.python import failure
3636

37-
from synapse.api.constants import EventTypes, Membership
37+
from synapse.api.constants import EduTypes, EventTypes, Membership
3838
from synapse.api.errors import (
3939
AuthError,
4040
Codes,
@@ -44,6 +44,7 @@
4444
SynapseError,
4545
UnsupportedRoomVersionError,
4646
)
47+
from synapse.api.ratelimiting import Ratelimiter
4748
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
4849
from synapse.events import EventBase
4950
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
@@ -869,6 +870,13 @@ def __init__(self, hs: "HomeServer"):
869870
# EDU received.
870871
self._edu_type_to_instance = {} # type: Dict[str, List[str]]
871872

873+
# A rate limiter for incoming room key requests per origin.
874+
self._room_key_request_rate_limiter = Ratelimiter(
875+
clock=self.clock,
876+
rate_hz=self.config.rc_key_requests.per_second,
877+
burst_count=self.config.rc_key_requests.burst_count,
878+
)
879+
872880
def register_edu_handler(
873881
self, edu_type: str, handler: Callable[[str, JsonDict], Awaitable[None]]
874882
):
@@ -917,7 +925,15 @@ def register_instances_for_edu(self, edu_type: str, instance_names: List[str]):
917925
self._edu_type_to_instance[edu_type] = instance_names
918926

919927
async def on_edu(self, edu_type: str, origin: str, content: dict):
920-
if not self.config.use_presence and edu_type == "m.presence":
928+
if not self.config.use_presence and edu_type == EduTypes.Presence:
929+
return
930+
931+
# If the incoming room key requests from a particular origin are over
932+
# the limit, drop them.
933+
if (
934+
edu_type == EduTypes.RoomKeyRequest
935+
and not self._room_key_request_rate_limiter.can_do_action(origin)
936+
):
921937
return
922938

923939
# Check if we have a handler on this instance

synapse/handlers/devicemessage.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import logging
1717
from typing import TYPE_CHECKING, Any, Dict
1818

19+
from synapse.api.constants import EduTypes
1920
from synapse.api.errors import SynapseError
21+
from synapse.api.ratelimiting import Ratelimiter
2022
from synapse.logging.context import run_in_background
2123
from synapse.logging.opentracing import (
2224
get_active_span_text_map,
@@ -25,7 +27,7 @@
2527
start_active_span,
2628
)
2729
from synapse.replication.http.devices import ReplicationUserDevicesResyncRestServlet
28-
from synapse.types import JsonDict, UserID, get_domain_from_id
30+
from synapse.types import JsonDict, Requester, UserID, get_domain_from_id
2931
from synapse.util import json_encoder
3032
from synapse.util.stringutils import random_string
3133

@@ -78,6 +80,12 @@ def __init__(self, hs: "HomeServer"):
7880
ReplicationUserDevicesResyncRestServlet.make_client(hs)
7981
)
8082

83+
self._ratelimiter = Ratelimiter(
84+
clock=hs.get_clock(),
85+
rate_hz=hs.config.rc_key_requests.per_second,
86+
burst_count=hs.config.rc_key_requests.burst_count,
87+
)
88+
8189
async def on_direct_to_device_edu(self, origin: str, content: JsonDict) -> None:
8290
local_messages = {}
8391
sender_user_id = content["sender"]
@@ -168,15 +176,27 @@ async def _check_for_unknown_devices(
168176

169177
async def send_device_message(
170178
self,
171-
sender_user_id: str,
179+
requester: Requester,
172180
message_type: str,
173181
messages: Dict[str, Dict[str, JsonDict]],
174182
) -> None:
183+
sender_user_id = requester.user.to_string()
184+
175185
set_tag("number_of_messages", len(messages))
176186
set_tag("sender", sender_user_id)
177187
local_messages = {}
178188
remote_messages = {} # type: Dict[str, Dict[str, Dict[str, JsonDict]]]
179189
for user_id, by_device in messages.items():
190+
# Ratelimit local cross-user key requests by the sending device.
191+
if (
192+
message_type == EduTypes.RoomKeyRequest
193+
and user_id != sender_user_id
194+
and self._ratelimiter.can_do_action(
195+
(sender_user_id, requester.device_id)
196+
)
197+
):
198+
continue
199+
180200
# we use UserID.from_string to catch invalid user ids
181201
if self.is_mine(UserID.from_string(user_id)):
182202
messages_by_device = {

synapse/handlers/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import random
1818
from typing import TYPE_CHECKING, Iterable, List, Optional
1919

20-
from synapse.api.constants import EventTypes, Membership
20+
from synapse.api.constants import EduTypes, EventTypes, Membership
2121
from synapse.api.errors import AuthError, SynapseError
2222
from synapse.events import EventBase
2323
from synapse.handlers.presence import format_user_presence_state
@@ -113,7 +113,7 @@ async def get_stream(
113113
states = await presence_handler.get_states(users)
114114
to_add.extend(
115115
{
116-
"type": EventTypes.Presence,
116+
"type": EduTypes.Presence,
117117
"content": format_user_presence_state(state, time_now),
118118
}
119119
for state in states

synapse/handlers/initial_sync.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from twisted.internet import defer
2020

21-
from synapse.api.constants import EventTypes, Membership
21+
from synapse.api.constants import EduTypes, EventTypes, Membership
2222
from synapse.api.errors import SynapseError
2323
from synapse.events.validator import EventValidator
2424
from synapse.handlers.presence import format_user_presence_state
@@ -412,7 +412,7 @@ async def get_presence():
412412

413413
return [
414414
{
415-
"type": EventTypes.Presence,
415+
"type": EduTypes.Presence,
416416
"content": format_user_presence_state(s, time_now),
417417
}
418418
for s in states

synapse/rest/client/v2_alpha/sendtodevice.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,8 @@ async def _put(self, request, message_type, txn_id):
5656
content = parse_json_object_from_request(request)
5757
assert_params_in_dict(content, ("messages",))
5858

59-
sender_user_id = requester.user.to_string()
60-
6159
await self.device_message_handler.send_device_message(
62-
sender_user_id, message_type, content["messages"]
60+
requester, message_type, content["messages"]
6361
)
6462

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

0 commit comments

Comments
 (0)