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

Commit 62ed877

Browse files
authored
Improve validation of field size limits in events. (#14664)
1 parent e2a1adb commit 62ed877

File tree

8 files changed

+119
-30
lines changed

8 files changed

+119
-30
lines changed

changelog.d/14664.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve validation of field size limits in events.

stubs/synapse/synapse_rust/push.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class PushRuleEvaluator:
4545
notification_power_levels: Mapping[str, int],
4646
related_events_flattened: Mapping[str, Mapping[str, str]],
4747
related_event_match_enabled: bool,
48-
room_version_feature_flags: list[str],
48+
room_version_feature_flags: Tuple[str, ...],
4949
msc3931_enabled: bool,
5050
): ...
5151
def run(

synapse/api/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ class EduTypes:
152152

153153
class RejectedReason:
154154
AUTH_ERROR: Final = "auth_error"
155+
OVERSIZED_EVENT: Final = "oversized_event"
155156

156157

157158
class RoomCreationPreset:

synapse/api/errors.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,17 @@ def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
424424
class EventSizeError(SynapseError):
425425
"""An error raised when an event is too big."""
426426

427-
def __init__(self, msg: str):
427+
def __init__(self, msg: str, unpersistable: bool):
428+
"""
429+
unpersistable:
430+
if True, the PDU must not be persisted, not even as a rejected PDU
431+
when received over federation.
432+
This is notably true when the entire PDU exceeds the size limit for a PDU,
433+
(as opposed to an individual key's size limit being exceeded).
434+
"""
435+
428436
super().__init__(413, msg, Codes.TOO_LARGE)
437+
self.unpersistable = unpersistable
429438

430439

431440
class LoginError(SynapseError):

synapse/api/room_versions.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import Callable, Dict, List, Optional
15+
from typing import Callable, Dict, Optional, Tuple
1616

1717
import attr
1818

@@ -103,7 +103,7 @@ class RoomVersion:
103103
# is not enough to mark it "supported": the push rule evaluator also needs to
104104
# support the flag. Unknown flags are ignored by the evaluator, making conditions
105105
# fail if used.
106-
msc3931_push_features: List[str] # values from PushRuleRoomFlag
106+
msc3931_push_features: Tuple[str, ...] # values from PushRuleRoomFlag
107107

108108

109109
class RoomVersions:
@@ -124,7 +124,7 @@ class RoomVersions:
124124
msc2716_redactions=False,
125125
msc3787_knock_restricted_join_rule=False,
126126
msc3667_int_only_power_levels=False,
127-
msc3931_push_features=[],
127+
msc3931_push_features=(),
128128
)
129129
V2 = RoomVersion(
130130
"2",
@@ -143,7 +143,7 @@ class RoomVersions:
143143
msc2716_redactions=False,
144144
msc3787_knock_restricted_join_rule=False,
145145
msc3667_int_only_power_levels=False,
146-
msc3931_push_features=[],
146+
msc3931_push_features=(),
147147
)
148148
V3 = RoomVersion(
149149
"3",
@@ -162,7 +162,7 @@ class RoomVersions:
162162
msc2716_redactions=False,
163163
msc3787_knock_restricted_join_rule=False,
164164
msc3667_int_only_power_levels=False,
165-
msc3931_push_features=[],
165+
msc3931_push_features=(),
166166
)
167167
V4 = RoomVersion(
168168
"4",
@@ -181,7 +181,7 @@ class RoomVersions:
181181
msc2716_redactions=False,
182182
msc3787_knock_restricted_join_rule=False,
183183
msc3667_int_only_power_levels=False,
184-
msc3931_push_features=[],
184+
msc3931_push_features=(),
185185
)
186186
V5 = RoomVersion(
187187
"5",
@@ -200,7 +200,7 @@ class RoomVersions:
200200
msc2716_redactions=False,
201201
msc3787_knock_restricted_join_rule=False,
202202
msc3667_int_only_power_levels=False,
203-
msc3931_push_features=[],
203+
msc3931_push_features=(),
204204
)
205205
V6 = RoomVersion(
206206
"6",
@@ -219,7 +219,7 @@ class RoomVersions:
219219
msc2716_redactions=False,
220220
msc3787_knock_restricted_join_rule=False,
221221
msc3667_int_only_power_levels=False,
222-
msc3931_push_features=[],
222+
msc3931_push_features=(),
223223
)
224224
MSC2176 = RoomVersion(
225225
"org.matrix.msc2176",
@@ -238,7 +238,7 @@ class RoomVersions:
238238
msc2716_redactions=False,
239239
msc3787_knock_restricted_join_rule=False,
240240
msc3667_int_only_power_levels=False,
241-
msc3931_push_features=[],
241+
msc3931_push_features=(),
242242
)
243243
V7 = RoomVersion(
244244
"7",
@@ -257,7 +257,7 @@ class RoomVersions:
257257
msc2716_redactions=False,
258258
msc3787_knock_restricted_join_rule=False,
259259
msc3667_int_only_power_levels=False,
260-
msc3931_push_features=[],
260+
msc3931_push_features=(),
261261
)
262262
V8 = RoomVersion(
263263
"8",
@@ -276,7 +276,7 @@ class RoomVersions:
276276
msc2716_redactions=False,
277277
msc3787_knock_restricted_join_rule=False,
278278
msc3667_int_only_power_levels=False,
279-
msc3931_push_features=[],
279+
msc3931_push_features=(),
280280
)
281281
V9 = RoomVersion(
282282
"9",
@@ -295,7 +295,7 @@ class RoomVersions:
295295
msc2716_redactions=False,
296296
msc3787_knock_restricted_join_rule=False,
297297
msc3667_int_only_power_levels=False,
298-
msc3931_push_features=[],
298+
msc3931_push_features=(),
299299
)
300300
MSC3787 = RoomVersion(
301301
"org.matrix.msc3787",
@@ -314,7 +314,7 @@ class RoomVersions:
314314
msc2716_redactions=False,
315315
msc3787_knock_restricted_join_rule=True,
316316
msc3667_int_only_power_levels=False,
317-
msc3931_push_features=[],
317+
msc3931_push_features=(),
318318
)
319319
V10 = RoomVersion(
320320
"10",
@@ -333,7 +333,7 @@ class RoomVersions:
333333
msc2716_redactions=False,
334334
msc3787_knock_restricted_join_rule=True,
335335
msc3667_int_only_power_levels=True,
336-
msc3931_push_features=[],
336+
msc3931_push_features=(),
337337
)
338338
MSC2716v4 = RoomVersion(
339339
"org.matrix.msc2716v4",
@@ -352,7 +352,7 @@ class RoomVersions:
352352
msc2716_redactions=True,
353353
msc3787_knock_restricted_join_rule=False,
354354
msc3667_int_only_power_levels=False,
355-
msc3931_push_features=[],
355+
msc3931_push_features=(),
356356
)
357357
MSC1767v10 = RoomVersion(
358358
# MSC1767 (Extensible Events) based on room version "10"
@@ -372,7 +372,7 @@ class RoomVersions:
372372
msc2716_redactions=False,
373373
msc3787_knock_restricted_join_rule=True,
374374
msc3667_int_only_power_levels=True,
375-
msc3931_push_features=[PushRuleRoomFlag.EXTENSIBLE_EVENTS],
375+
msc3931_push_features=(PushRuleRoomFlag.EXTENSIBLE_EVENTS,),
376376
)
377377

378378

synapse/event_auth.py

+69-7
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
KNOWN_ROOM_VERSIONS,
5353
EventFormatVersions,
5454
RoomVersion,
55+
RoomVersions,
5556
)
5657
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
5758
from synapse.types import MutableStateMap, StateMap, UserID, get_domain_from_id
@@ -341,19 +342,80 @@ def check_state_dependent_auth_rules(
341342
logger.debug("Allowing! %s", event)
342343

343344

345+
# Set of room versions where Synapse did not apply event key size limits
346+
# in bytes, but rather in codepoints.
347+
# In these room versions, we are more lenient with event size validation.
348+
LENIENT_EVENT_BYTE_LIMITS_ROOM_VERSIONS = {
349+
RoomVersions.V1,
350+
RoomVersions.V2,
351+
RoomVersions.V3,
352+
RoomVersions.V4,
353+
RoomVersions.V5,
354+
RoomVersions.V6,
355+
RoomVersions.MSC2176,
356+
RoomVersions.V7,
357+
RoomVersions.V8,
358+
RoomVersions.V9,
359+
RoomVersions.MSC3787,
360+
RoomVersions.V10,
361+
RoomVersions.MSC2716v4,
362+
RoomVersions.MSC1767v10,
363+
}
364+
365+
344366
def _check_size_limits(event: "EventBase") -> None:
367+
"""
368+
Checks the size limits in a PDU.
369+
370+
The entire size limit of the PDU is checked first.
371+
Then the size of fields is checked, first in codepoints and then in bytes.
372+
373+
The codepoint size limits are only for Synapse compatibility.
374+
375+
Raises:
376+
EventSizeError:
377+
when a size limit has been violated.
378+
379+
unpersistable=True if Synapse never would have accepted the event and
380+
the PDU must NOT be persisted.
381+
382+
unpersistable=False if a prior version of Synapse would have accepted the
383+
event and so the PDU must be persisted as rejected to avoid
384+
breaking the room.
385+
"""
386+
387+
# Whole PDU check
388+
if len(encode_canonical_json(event.get_pdu_json())) > MAX_PDU_SIZE:
389+
raise EventSizeError("event too large", unpersistable=True)
390+
391+
# Codepoint size check: Synapse always enforced these limits, so apply
392+
# them strictly.
345393
if len(event.user_id) > 255:
346-
raise EventSizeError("'user_id' too large")
394+
raise EventSizeError("'user_id' too large", unpersistable=True)
347395
if len(event.room_id) > 255:
348-
raise EventSizeError("'room_id' too large")
396+
raise EventSizeError("'room_id' too large", unpersistable=True)
349397
if event.is_state() and len(event.state_key) > 255:
350-
raise EventSizeError("'state_key' too large")
398+
raise EventSizeError("'state_key' too large", unpersistable=True)
351399
if len(event.type) > 255:
352-
raise EventSizeError("'type' too large")
400+
raise EventSizeError("'type' too large", unpersistable=True)
353401
if len(event.event_id) > 255:
354-
raise EventSizeError("'event_id' too large")
355-
if len(encode_canonical_json(event.get_pdu_json())) > MAX_PDU_SIZE:
356-
raise EventSizeError("event too large")
402+
raise EventSizeError("'event_id' too large", unpersistable=True)
403+
404+
strict_byte_limits = (
405+
event.room_version not in LENIENT_EVENT_BYTE_LIMITS_ROOM_VERSIONS
406+
)
407+
408+
# Byte size check: if these fail, then be lenient to avoid breaking rooms.
409+
if len(event.user_id.encode("utf-8")) > 255:
410+
raise EventSizeError("'user_id' too large", unpersistable=strict_byte_limits)
411+
if len(event.room_id.encode("utf-8")) > 255:
412+
raise EventSizeError("'room_id' too large", unpersistable=strict_byte_limits)
413+
if event.is_state() and len(event.state_key.encode("utf-8")) > 255:
414+
raise EventSizeError("'state_key' too large", unpersistable=strict_byte_limits)
415+
if len(event.type.encode("utf-8")) > 255:
416+
raise EventSizeError("'type' too large", unpersistable=strict_byte_limits)
417+
if len(event.event_id.encode("utf-8")) > 255:
418+
raise EventSizeError("'event_id' too large", unpersistable=strict_byte_limits)
357419

358420

359421
def _check_create(event: "EventBase") -> None:

synapse/handlers/federation_event.py

+20
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from synapse.api.errors import (
4444
AuthError,
4545
Codes,
46+
EventSizeError,
4647
FederationError,
4748
FederationPullAttemptBackoffError,
4849
HttpResponseException,
@@ -1736,6 +1737,15 @@ async def prep(event: EventBase) -> None:
17361737
except AuthError as e:
17371738
logger.warning("Rejecting %r because %s", event, e)
17381739
context.rejected = RejectedReason.AUTH_ERROR
1740+
except EventSizeError as e:
1741+
if e.unpersistable:
1742+
# This event is completely unpersistable.
1743+
raise e
1744+
# Otherwise, we are somewhat lenient and just persist the event
1745+
# as rejected, for moderate compatibility with older Synapse
1746+
# versions.
1747+
logger.warning("While validating received event %r: %s", event, e)
1748+
context.rejected = RejectedReason.OVERSIZED_EVENT
17391749

17401750
events_and_contexts_to_persist.append((event, context))
17411751

@@ -1781,6 +1791,16 @@ async def _check_event_auth(
17811791
# TODO: use a different rejected reason here?
17821792
context.rejected = RejectedReason.AUTH_ERROR
17831793
return
1794+
except EventSizeError as e:
1795+
if e.unpersistable:
1796+
# This event is completely unpersistable.
1797+
raise e
1798+
# Otherwise, we are somewhat lenient and just persist the event
1799+
# as rejected, for moderate compatibility with older Synapse
1800+
# versions.
1801+
logger.warning("While validating received event %r: %s", event, e)
1802+
context.rejected = RejectedReason.OVERSIZED_EVENT
1803+
return
17841804

17851805
# next, check that we have all of the event's auth events.
17861806
#

synapse/push/bulk_push_rule_evaluator.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -342,18 +342,14 @@ async def _action_for_event_by_user(
342342
for user_id, level in notification_levels.items():
343343
notification_levels[user_id] = int(level)
344344

345-
room_version_features = event.room_version.msc3931_push_features
346-
if not room_version_features:
347-
room_version_features = []
348-
349345
evaluator = PushRuleEvaluator(
350346
_flatten_dict(event, room_version=event.room_version),
351347
room_member_count,
352348
sender_power_level,
353349
notification_levels,
354350
related_events,
355351
self._related_event_match_enabled,
356-
room_version_features,
352+
event.room_version.msc3931_push_features,
357353
self.hs.config.experimental.msc1767_enabled, # MSC3931 flag
358354
)
359355

0 commit comments

Comments
 (0)