Skip to content

Commit 657dd51

Browse files
committed
Merge branch 'master' into develop
2 parents b257c7a + 6f689d4 commit 657dd51

File tree

14 files changed

+419
-23
lines changed

14 files changed

+419
-23
lines changed

.github/workflows/release-artifacts.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ name: Build release artifacts
55
on:
66
# we build on PRs and develop to (hopefully) get early warning
77
# of things breaking (but only build one set of debs). PRs skip
8-
# building wheels on macOS & ARM.
8+
# building wheels on ARM.
99
pull_request:
1010
push:
1111
branches: ["develop", "release-*"]
@@ -111,20 +111,14 @@ jobs:
111111
runs-on: ${{ matrix.os }}
112112
strategy:
113113
matrix:
114-
os: [ubuntu-22.04, macos-13]
114+
os: [ubuntu-22.04]
115115
arch: [x86_64, aarch64]
116116
# is_pr is a flag used to exclude certain jobs from the matrix on PRs.
117117
# It is not read by the rest of the workflow.
118118
is_pr:
119119
- ${{ startsWith(github.ref, 'refs/pull/') }}
120120

121121
exclude:
122-
# Don't build macos wheels on PR CI.
123-
- is_pr: true
124-
os: "macos-13"
125-
# Don't build aarch64 wheels on mac.
126-
- os: "macos-13"
127-
arch: aarch64
128122
# Don't build aarch64 wheels on PR CI.
129123
- is_pr: true
130124
arch: aarch64

CHANGES.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,53 @@
1+
# Synapse 1.120.2 (2024-12-03)
2+
3+
This version has building of wheels for macOS disabled.
4+
It is functionally identical to 1.120.1, which contains multiple security fixes.
5+
If you are already using 1.120.1, there is no need to upgrade to this version.
6+
7+
8+
9+
# Synapse 1.120.1 (2024-12-03)
10+
11+
This patch release fixes multiple security vulnerabilities, some affecting all prior versions of Synapse. Server administrators are encouraged to update Synapse as soon as possible. We are not aware of these vulnerabilities being exploited in the wild.
12+
13+
Administrators who are unable to update Synapse may use the workarounds described in the linked GitHub Security Advisory below.
14+
15+
### Security advisory
16+
17+
The following issues are fixed in 1.120.1.
18+
19+
- [GHSA-rfq8-j7rh-8hf2](https://github.com/element-hq/synapse/security/advisories/GHSA-rfq8-j7rh-8hf2) / [CVE-2024-52805](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-52805): **Unsupported content types can lead to memory exhaustion**
20+
21+
Synapse instances which have a high `max_upload_size` and which don't have a reverse proxy in front of them that would otherwise limit upload size are affected.
22+
23+
Fixed by [4b7154c58501b4bf5e1c2d6c11ebef96529f2fdf](https://github.com/element-hq/synapse/commit/4b7154c58501b4bf5e1c2d6c11ebef96529f2fdf).
24+
25+
- [GHSA-f3r3-h2mq-hx2h](https://github.com/element-hq/synapse/security/advisories/GHSA-f3r3-h2mq-hx2h) / [CVE-2024-52815](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-52815): **Malicious invites via federation can break a user's sync**
26+
27+
Fixed by [d82e1ed357b7ee21dff83d06cba7a67840cfd464](https://github.com/element-hq/synapse/commit/d82e1ed357b7ee21dff83d06cba7a67840cfd464).
28+
29+
- [GHSA-vp6v-whfm-rv3g](https://github.com/element-hq/synapse/security/advisories/GHSA-vp6v-whfm-rv3g) / [CVE-2024-53863](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-53863): **Synapse can be forced to thumbnail unexpected file formats, invoking potentially untrustworthy decoders**
30+
31+
Synapse instances can disable dynamic thumbnailing by setting `dynamic_thumbnails` to `false` in the configuration file.
32+
33+
Fixed by [b64a4e5fbbbf119b6c65aedf0d999b4237d55503](https://github.com/element-hq/synapse/commit/b64a4e5fbbbf119b6c65aedf0d999b4237d55503).
34+
35+
- [GHSA-56w4-5538-8v8h](https://github.com/element-hq/synapse/security/advisories/GHSA-56w4-5538-8v8h) / [CVE-2024-53867](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-53867): **The Sliding Sync feature on Synapse versions between 1.113.0rc1 and 1.120.0 can leak partial room state changes to users no longer in a room**
36+
37+
Non-state events, like messages, are unaffected.
38+
39+
Synapse instances can disable the Sliding Sync feature by setting `experimental_features.msc3575_enabled` to `false` in the configuration file.
40+
41+
Fixed by [4daa533e82f345ce87b9495d31781af570ba3ead](https://github.com/element-hq/synapse/commit/4daa533e82f345ce87b9495d31781af570ba3ead).
42+
43+
See the advisories for more details. If you have any questions, email [security at element.io](mailto:[email protected]).
44+
45+
### Bugfixes
46+
47+
- Fix release process to not create duplicate releases. ([\#17970](https://github.com/element-hq/synapse/issues/17970))
48+
49+
50+
151
# Synapse 1.120.0 (2024-11-26)
252

353
### Bugfixes

debian/changelog

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
matrix-synapse-py3 (1.120.2) stable; urgency=medium
2+
3+
* New synapse release 1.120.2.
4+
5+
-- Synapse Packaging team <[email protected]> Tue, 03 Dec 2024 15:43:37 +0000
6+
7+
matrix-synapse-py3 (1.120.1) stable; urgency=medium
8+
9+
* New synapse release 1.120.1.
10+
11+
-- Synapse Packaging team <[email protected]> Tue, 03 Dec 2024 09:07:57 +0000
12+
113
matrix-synapse-py3 (1.120.0) stable; urgency=medium
214

315
* New synapse release 1.120.0.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ module-name = "synapse.synapse_rust"
9797

9898
[tool.poetry]
9999
name = "matrix-synapse"
100-
version = "1.120.0"
100+
version = "1.120.2"
101101
description = "Homeserver for the Matrix decentralised comms protocol"
102102
authors = ["Matrix.org Team and Contributors <[email protected]>"]
103103
license = "AGPL-3.0-or-later"

synapse/federation/transport/server/federation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ async def on_PUT(
509509
event = content["event"]
510510
invite_room_state = content.get("invite_room_state", [])
511511

512+
if not isinstance(invite_room_state, list):
513+
invite_room_state = []
514+
512515
# Synapse expects invite_room_state to be in unsigned, as it is in v1
513516
# API
514517

synapse/handlers/federation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,9 @@ async def do_knock(
880880
if stripped_room_state is None:
881881
raise KeyError("Missing 'knock_room_state' field in send_knock response")
882882

883+
if not isinstance(stripped_room_state, list):
884+
raise TypeError("'knock_room_state' has wrong type")
885+
883886
event.unsigned["knock_room_state"] = stripped_room_state
884887

885888
context = EventContext.for_outlier(self._storage_controllers)

synapse/handlers/sliding_sync/__init__.py

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
trace,
4040
)
4141
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
42+
from synapse.storage.databases.main.state_deltas import StateDelta
4243
from synapse.storage.databases.main.stream import PaginateFunction
4344
from synapse.storage.roommember import (
4445
MemberSummary,
@@ -48,6 +49,7 @@
4849
MutableStateMap,
4950
PersistedEventPosition,
5051
Requester,
52+
RoomStreamToken,
5153
SlidingSyncStreamToken,
5254
StateMap,
5355
StrCollection,
@@ -470,6 +472,64 @@ async def get_current_state_at(
470472

471473
return state_map
472474

475+
@trace
476+
async def get_current_state_deltas_for_room(
477+
self,
478+
room_id: str,
479+
room_membership_for_user_at_to_token: RoomsForUserType,
480+
from_token: RoomStreamToken,
481+
to_token: RoomStreamToken,
482+
) -> List[StateDelta]:
483+
"""
484+
Get the state deltas between two tokens taking into account the user's
485+
membership. If the user is LEAVE/BAN, we will only get the state deltas up to
486+
their LEAVE/BAN event (inclusive).
487+
488+
(> `from_token` and <= `to_token`)
489+
"""
490+
membership = room_membership_for_user_at_to_token.membership
491+
# We don't know how to handle `membership` values other than these. The
492+
# code below would need to be updated.
493+
assert membership in (
494+
Membership.JOIN,
495+
Membership.INVITE,
496+
Membership.KNOCK,
497+
Membership.LEAVE,
498+
Membership.BAN,
499+
)
500+
501+
# People shouldn't see past their leave/ban event
502+
if membership in (
503+
Membership.LEAVE,
504+
Membership.BAN,
505+
):
506+
to_bound = (
507+
room_membership_for_user_at_to_token.event_pos.to_room_stream_token()
508+
)
509+
# If we are participating in the room, we can get the latest current state in
510+
# the room
511+
elif membership == Membership.JOIN:
512+
to_bound = to_token
513+
# We can only rely on the stripped state included in the invite/knock event
514+
# itself so there will never be any state deltas to send down.
515+
elif membership in (Membership.INVITE, Membership.KNOCK):
516+
return []
517+
else:
518+
# We don't know how to handle this type of membership yet
519+
#
520+
# FIXME: We should use `assert_never` here but for some reason
521+
# the exhaustive matching doesn't recognize the `Never` here.
522+
# assert_never(membership)
523+
raise AssertionError(
524+
f"Unexpected membership {membership} that we don't know how to handle yet"
525+
)
526+
527+
return await self.store.get_current_state_deltas_for_room(
528+
room_id=room_id,
529+
from_token=from_token,
530+
to_token=to_bound,
531+
)
532+
473533
@trace
474534
async def get_room_sync_data(
475535
self,
@@ -755,13 +815,19 @@ async def get_room_sync_data(
755815

756816
stripped_state = []
757817
if invite_or_knock_event.membership == Membership.INVITE:
758-
stripped_state.extend(
759-
invite_or_knock_event.unsigned.get("invite_room_state", [])
818+
invite_state = invite_or_knock_event.unsigned.get(
819+
"invite_room_state", []
760820
)
821+
if not isinstance(invite_state, list):
822+
invite_state = []
823+
824+
stripped_state.extend(invite_state)
761825
elif invite_or_knock_event.membership == Membership.KNOCK:
762-
stripped_state.extend(
763-
invite_or_knock_event.unsigned.get("knock_room_state", [])
764-
)
826+
knock_state = invite_or_knock_event.unsigned.get("knock_room_state", [])
827+
if not isinstance(knock_state, list):
828+
knock_state = []
829+
830+
stripped_state.extend(knock_state)
765831

766832
stripped_state.append(strip_event(invite_or_knock_event))
767833

@@ -790,8 +856,9 @@ async def get_room_sync_data(
790856
# TODO: Limit the number of state events we're about to send down
791857
# the room, if its too many we should change this to an
792858
# `initial=True`?
793-
deltas = await self.store.get_current_state_deltas_for_room(
859+
deltas = await self.get_current_state_deltas_for_room(
794860
room_id=room_id,
861+
room_membership_for_user_at_to_token=room_membership_for_user_at_to_token,
795862
from_token=from_bound,
796863
to_token=to_token.room_key,
797864
)

synapse/http/site.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import contextlib
2222
import logging
2323
import time
24+
from http import HTTPStatus
2425
from typing import TYPE_CHECKING, Any, Generator, Optional, Tuple, Union
2526

2627
import attr
@@ -139,6 +140,41 @@ def __repr__(self) -> str:
139140
self.synapse_site.site_tag,
140141
)
141142

143+
# Twisted machinery: this method is called by the Channel once the full request has
144+
# been received, to dispatch the request to a resource.
145+
#
146+
# We're patching Twisted to bail/abort early when we see someone trying to upload
147+
# `multipart/form-data` so we can avoid Twisted parsing the entire request body into
148+
# in-memory (specific problem of this specific `Content-Type`). This protects us
149+
# from an attacker uploading something bigger than the available RAM and crashing
150+
# the server with a `MemoryError`, or carefully block just enough resources to cause
151+
# all other requests to fail.
152+
#
153+
# FIXME: This can be removed once we Twisted releases a fix and we update to a
154+
# version that is patched
155+
def requestReceived(self, command: bytes, path: bytes, version: bytes) -> None:
156+
if command == b"POST":
157+
ctype = self.requestHeaders.getRawHeaders(b"content-type")
158+
if ctype and b"multipart/form-data" in ctype[0]:
159+
self.method, self.uri = command, path
160+
self.clientproto = version
161+
self.code = HTTPStatus.UNSUPPORTED_MEDIA_TYPE.value
162+
self.code_message = bytes(
163+
HTTPStatus.UNSUPPORTED_MEDIA_TYPE.phrase, "ascii"
164+
)
165+
self.responseHeaders.setRawHeaders(b"content-length", [b"0"])
166+
167+
logger.warning(
168+
"Aborting connection from %s because `content-type: multipart/form-data` is unsupported: %s %s",
169+
self.client,
170+
command,
171+
path,
172+
)
173+
self.write(b"")
174+
self.loseConnection()
175+
return
176+
return super().requestReceived(command, path, version)
177+
142178
def handleContentChunk(self, data: bytes) -> None:
143179
# we should have a `content` by now.
144180
assert self.content, "handleContentChunk() called before gotLength()"

synapse/media/thumbnailer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ class ThumbnailError(Exception):
6767
class Thumbnailer:
6868
FORMATS = {"image/jpeg": "JPEG", "image/png": "PNG"}
6969

70+
# Which image formats we allow Pillow to open.
71+
# This should intentionally be kept restrictive, because the decoder of any
72+
# format in this list becomes part of our trusted computing base.
73+
PILLOW_FORMATS = ("jpeg", "png", "webp", "gif")
74+
7075
@staticmethod
7176
def set_limits(max_image_pixels: int) -> None:
7277
Image.MAX_IMAGE_PIXELS = max_image_pixels
@@ -76,7 +81,7 @@ def __init__(self, input_path: str):
7681
self._closed = False
7782

7883
try:
79-
self.image = Image.open(input_path)
84+
self.image = Image.open(input_path, formats=self.PILLOW_FORMATS)
8085
except OSError as e:
8186
# If an error occurs opening the image, a thumbnail won't be able to
8287
# be generated.

synapse/push/push_tools.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ async def get_context_for_event(
7474

7575
room_state = []
7676
if ev.content.get("membership") == Membership.INVITE:
77-
room_state = ev.unsigned.get("invite_room_state", [])
77+
invite_room_state = ev.unsigned.get("invite_room_state", [])
78+
if isinstance(invite_room_state, list):
79+
room_state = invite_room_state
7880
elif ev.content.get("membership") == Membership.KNOCK:
79-
room_state = ev.unsigned.get("knock_room_state", [])
81+
knock_room_state = ev.unsigned.get("knock_room_state", [])
82+
if isinstance(knock_room_state, list):
83+
room_state = knock_room_state
8084

8185
# Ideally we'd reuse the logic in `calculate_room_name`, but that gets
8286
# complicated to handle partial events vs pulling events from the DB.

0 commit comments

Comments
 (0)