Skip to content

Commit 8bcbeb4

Browse files
committed
Merge tag 'v1.41.1' into 2021-05
Synapse 1.41.1 (2021-08-31) =========================== Due to the two security issues highlighted below, server administrators are encouraged to update Synapse. We are not aware of these vulnerabilities being exploited in the wild. Security advisory ----------------- The following issues are fixed in v1.41.1. - **[GHSA-3x4c-pq33-4w3q](GHSA-3x4c-pq33-4w3q) / [CVE-2021-39164](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39164): Enumerating a private room's list of members and their display names.** If an unauthorized user both knows the Room ID of a private room *and* that room's history visibility is set to `shared`, then they may be able to enumerate the room's members, including their display names. The unauthorized user must be on the same homeserver as a user who is a member of the target room. Fixed by [52c7a51](matrix-org@52c7a51cf). - **[GHSA-jj53-8fmw-f2w2](GHSA-jj53-8fmw-f2w2) / [CVE-2021-39163](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39163): Disclosing a private room's name, avatar, topic, and number of members.** If an unauthorized user knows the Room ID of a private room, then its name, avatar, topic, and number of members may be disclosed through Group / Community features. The unauthorized user must be on the same homeserver as a user who is a member of the target room, and their homeserver must allow non-administrators to create groups (`enable_group_creation` in the Synapse configuration; off by default). Fixed by [cb35df9](matrix-org@cb35df940a), [\matrix-org#10723](matrix-org#10723). Bugfixes -------- - Fix a regression introduced in Synapse 1.41 which broke email transmission on systems using older versions of the Twisted library. ([\matrix-org#10713](matrix-org#10713))
2 parents e1fe53a + a4c8a2f commit 8bcbeb4

File tree

13 files changed

+388
-28
lines changed

13 files changed

+388
-28
lines changed

CHANGES.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
Synapse 1.41.1 (2021-08-31)
2+
===========================
3+
4+
Due to the two security issues highlighted below, server administrators are encouraged to update Synapse. We are not aware of these vulnerabilities being exploited in the wild.
5+
6+
Security advisory
7+
-----------------
8+
9+
The following issues are fixed in v1.41.1.
10+
11+
- **[GHSA-3x4c-pq33-4w3q](https://github.com/matrix-org/synapse/security/advisories/GHSA-3x4c-pq33-4w3q) / [CVE-2021-39164](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39164): Enumerating a private room's list of members and their display names.**
12+
13+
If an unauthorized user both knows the Room ID of a private room *and* that room's history visibility is set to `shared`, then they may be able to enumerate the room's members, including their display names.
14+
15+
The unauthorized user must be on the same homeserver as a user who is a member of the target room.
16+
17+
Fixed by [52c7a51cf](https://github.com/matrix-org/synapse/commit/52c7a51cf).
18+
19+
- **[GHSA-jj53-8fmw-f2w2](https://github.com/matrix-org/synapse/security/advisories/GHSA-jj53-8fmw-f2w2) / [CVE-2021-39163](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39163): Disclosing a private room's name, avatar, topic, and number of members.**
20+
21+
If an unauthorized user knows the Room ID of a private room, then its name, avatar, topic, and number of members may be disclosed through Group / Community features.
22+
23+
The unauthorized user must be on the same homeserver as a user who is a member of the target room, and their homeserver must allow non-administrators to create groups (`enable_group_creation` in the Synapse configuration; off by default).
24+
25+
Fixed by [cb35df940a](https://github.com/matrix-org/synapse/commit/cb35df940a), [\#10723](https://github.com/matrix-org/synapse/issues/10723).
26+
27+
Bugfixes
28+
--------
29+
30+
- Fix a regression introduced in Synapse 1.41 which broke email transmission on systems using older versions of the Twisted library. ([\#10713](https://github.com/matrix-org/synapse/issues/10713))
31+
32+
133
Synapse 1.41.0 (2021-08-24)
234
===========================
335

changelog.d/10713.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a regression introduced in Synapse 1.41 which broke email transmission on Systems using older versions of the Twisted library.

changelog.d/10723.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix unauthorised exposure of room metadata to communities.

debian/changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
matrix-synapse-py3 (1.41.1) stable; urgency=high
2+
3+
* New synapse release 1.41.1.
4+
5+
-- Synapse Packaging team <packages@matrix.org> Tue, 31 Aug 2021 12:59:10 +0100
6+
17
matrix-synapse-py3 (1.41.0) stable; urgency=medium
28

39
* New synapse release 1.41.0.

mypy.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ files =
8787
tests/test_utils,
8888
tests/handlers/test_password_providers.py,
8989
tests/handlers/test_room_summary.py,
90+
tests/handlers/test_send_email.py,
9091
tests/rest/client/v1/test_login.py,
9192
tests/rest/client/v2_alpha/test_auth.py,
9293
tests/util/test_itertools.py,

synapse/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
except ImportError:
4848
pass
4949

50-
__version__ = "1.41.0"
50+
__version__ = "1.41.1"
5151

5252
if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
5353
# We import here so that we don't have to install a bunch of deps when

synapse/groups/groups_server.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,13 @@ async def get_rooms_in_group(
332332
requester_user_id, group_id
333333
)
334334

335+
# Note! room_results["is_public"] is about whether the room is considered
336+
# public from the group's point of view. (i.e. whether non-group members
337+
# should be able to see the room is in the group).
338+
# This is not the same as whether the room itself is public (in the sense
339+
# of being visible in the room directory).
340+
# As such, room_results["is_public"] itself is not sufficient to determine
341+
# whether any given user is permitted to see the room's metadata.
335342
room_results = await self.store.get_rooms_in_group(
336343
group_id, include_private=is_user_in_group
337344
)
@@ -341,8 +348,15 @@ async def get_rooms_in_group(
341348
room_id = room_result["room_id"]
342349

343350
joined_users = await self.store.get_users_in_room(room_id)
351+
352+
# check the user is actually allowed to see the room before showing it to them
353+
allow_private = requester_user_id in joined_users
354+
344355
entry = await self.room_list_handler.generate_room_entry(
345-
room_id, len(joined_users), with_alias=False, allow_private=True
356+
room_id,
357+
len(joined_users),
358+
with_alias=False,
359+
allow_private=allow_private,
346360
)
347361

348362
if not entry:
@@ -354,7 +368,7 @@ async def get_rooms_in_group(
354368

355369
chunk.sort(key=lambda e: -e["num_joined_members"])
356370

357-
return {"chunk": chunk, "total_room_count_estimate": len(room_results)}
371+
return {"chunk": chunk, "total_room_count_estimate": len(chunk)}
358372

359373

360374
class GroupsServerHandler(GroupsServerWorkerHandler):

synapse/handlers/message.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,37 @@ async def get_state_events(
183183

184184
if not last_events:
185185
raise NotFoundError("Can't find event for token %s" % (at_token,))
186+
last_event = last_events[0]
187+
188+
# check whether the user is in the room at that time to determine
189+
# whether they should be treated as peeking.
190+
state_map = await self.state_store.get_state_for_event(
191+
last_event.event_id,
192+
StateFilter.from_types([(EventTypes.Member, user_id)]),
193+
)
194+
195+
joined = False
196+
membership_event = state_map.get((EventTypes.Member, user_id))
197+
if membership_event:
198+
joined = membership_event.membership == Membership.JOIN
199+
200+
is_peeking = not joined
186201

187202
visible_events = await filter_events_for_client(
188203
self.storage,
189204
user_id,
190205
last_events,
191206
filter_send_to_client=False,
207+
is_peeking=is_peeking,
192208
)
193209

194-
event = last_events[0]
195210
if visible_events:
196211
room_state_events = await self.state_store.get_state_for_events(
197-
[event.event_id], state_filter=state_filter
212+
[last_event.event_id], state_filter=state_filter
198213
)
199-
room_state: Mapping[Any, EventBase] = room_state_events[event.event_id]
214+
room_state: Mapping[Any, EventBase] = room_state_events[
215+
last_event.event_id
216+
]
200217
else:
201218
raise AuthError(
202219
403,

synapse/handlers/send_email.py

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
from io import BytesIO
2020
from typing import TYPE_CHECKING, Optional
2121

22+
from pkg_resources import parse_version
23+
24+
import twisted
2225
from twisted.internet.defer import Deferred
23-
from twisted.internet.interfaces import IReactorTCP
24-
from twisted.mail.smtp import ESMTPSenderFactory
26+
from twisted.internet.interfaces import IOpenSSLContextFactory, IReactorTCP
27+
from twisted.mail.smtp import ESMTPSender, ESMTPSenderFactory
2528

2629
from synapse.logging.context import make_deferred_yieldable
2730

@@ -30,6 +33,19 @@
3033

3134
logger = logging.getLogger(__name__)
3235

36+
_is_old_twisted = parse_version(twisted.__version__) < parse_version("21")
37+
38+
39+
class _NoTLSESMTPSender(ESMTPSender):
40+
"""Extend ESMTPSender to disable TLS
41+
42+
Unfortunately, before Twisted 21.2, ESMTPSender doesn't give an easy way to disable
43+
TLS, so we override its internal method which it uses to generate a context factory.
44+
"""
45+
46+
def _getContextFactory(self) -> Optional[IOpenSSLContextFactory]:
47+
return None
48+
3349

3450
async def _sendmail(
3551
reactor: IReactorTCP,
@@ -42,7 +58,7 @@ async def _sendmail(
4258
password: Optional[bytes] = None,
4359
require_auth: bool = False,
4460
require_tls: bool = False,
45-
tls_hostname: Optional[str] = None,
61+
enable_tls: bool = True,
4662
) -> None:
4763
"""A simple wrapper around ESMTPSenderFactory, to allow substitution in tests
4864
@@ -57,24 +73,37 @@ async def _sendmail(
5773
password: password to give when authenticating
5874
require_auth: if auth is not offered, fail the request
5975
require_tls: if TLS is not offered, fail the reqest
60-
tls_hostname: TLS hostname to check for. None to disable TLS.
76+
enable_tls: True to enable TLS. If this is False and require_tls is True,
77+
the request will fail.
6178
"""
6279
msg = BytesIO(msg_bytes)
63-
6480
d: "Deferred[object]" = Deferred()
6581

66-
factory = ESMTPSenderFactory(
67-
username,
68-
password,
69-
from_addr,
70-
to_addr,
71-
msg,
72-
d,
73-
heloFallback=True,
74-
requireAuthentication=require_auth,
75-
requireTransportSecurity=require_tls,
76-
hostname=tls_hostname,
77-
)
82+
def build_sender_factory(**kwargs) -> ESMTPSenderFactory:
83+
return ESMTPSenderFactory(
84+
username,
85+
password,
86+
from_addr,
87+
to_addr,
88+
msg,
89+
d,
90+
heloFallback=True,
91+
requireAuthentication=require_auth,
92+
requireTransportSecurity=require_tls,
93+
**kwargs,
94+
)
95+
96+
if _is_old_twisted:
97+
# before twisted 21.2, we have to override the ESMTPSender protocol to disable
98+
# TLS
99+
factory = build_sender_factory()
100+
101+
if not enable_tls:
102+
factory.protocol = _NoTLSESMTPSender
103+
else:
104+
# for twisted 21.2 and later, there is a 'hostname' parameter which we should
105+
# set to enable TLS.
106+
factory = build_sender_factory(hostname=smtphost if enable_tls else None)
78107

79108
# the IReactorTCP interface claims host has to be a bytes, which seems to be wrong
80109
reactor.connectTCP(smtphost, smtpport, factory, timeout=30, bindAddress=None) # type: ignore[arg-type]
@@ -154,5 +183,5 @@ async def send_email(
154183
password=self._smtp_pass,
155184
require_auth=self._smtp_user is not None,
156185
require_tls=self._require_transport_security,
157-
tls_hostname=self._smtp_host if self._enable_tls else None,
186+
enable_tls=self._enable_tls,
158187
)

tests/handlers/test_send_email.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2021 The Matrix.org Foundation C.I.C.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
from typing import List, Tuple
17+
18+
from zope.interface import implementer
19+
20+
from twisted.internet import defer
21+
from twisted.internet.address import IPv4Address
22+
from twisted.internet.defer import ensureDeferred
23+
from twisted.mail import interfaces, smtp
24+
25+
from tests.server import FakeTransport
26+
from tests.unittest import HomeserverTestCase
27+
28+
29+
@implementer(interfaces.IMessageDelivery)
30+
class _DummyMessageDelivery:
31+
def __init__(self):
32+
# (recipient, message) tuples
33+
self.messages: List[Tuple[smtp.Address, bytes]] = []
34+
35+
def receivedHeader(self, helo, origin, recipients):
36+
return None
37+
38+
def validateFrom(self, helo, origin):
39+
return origin
40+
41+
def record_message(self, recipient: smtp.Address, message: bytes):
42+
self.messages.append((recipient, message))
43+
44+
def validateTo(self, user: smtp.User):
45+
return lambda: _DummyMessage(self, user)
46+
47+
48+
@implementer(interfaces.IMessageSMTP)
49+
class _DummyMessage:
50+
"""IMessageSMTP implementation which saves the message delivered to it
51+
to the _DummyMessageDelivery object.
52+
"""
53+
54+
def __init__(self, delivery: _DummyMessageDelivery, user: smtp.User):
55+
self._delivery = delivery
56+
self._user = user
57+
self._buffer: List[bytes] = []
58+
59+
def lineReceived(self, line):
60+
self._buffer.append(line)
61+
62+
def eomReceived(self):
63+
message = b"\n".join(self._buffer) + b"\n"
64+
self._delivery.record_message(self._user.dest, message)
65+
return defer.succeed(b"saved")
66+
67+
def connectionLost(self):
68+
pass
69+
70+
71+
class SendEmailHandlerTestCase(HomeserverTestCase):
72+
def test_send_email(self):
73+
"""Happy-path test that we can send email to a non-TLS server."""
74+
h = self.hs.get_send_email_handler()
75+
d = ensureDeferred(
76+
h.send_email(
77+
"foo@bar.com", "test subject", "Tests", "HTML content", "Text content"
78+
)
79+
)
80+
# there should be an attempt to connect to localhost:25
81+
self.assertEqual(len(self.reactor.tcpClients), 1)
82+
(host, port, client_factory, _timeout, _bindAddress) = self.reactor.tcpClients[
83+
0
84+
]
85+
self.assertEqual(host, "localhost")
86+
self.assertEqual(port, 25)
87+
88+
# wire it up to an SMTP server
89+
message_delivery = _DummyMessageDelivery()
90+
server_protocol = smtp.ESMTP()
91+
server_protocol.delivery = message_delivery
92+
# make sure that the server uses the test reactor to set timeouts
93+
server_protocol.callLater = self.reactor.callLater # type: ignore[assignment]
94+
95+
client_protocol = client_factory.buildProtocol(None)
96+
client_protocol.makeConnection(FakeTransport(server_protocol, self.reactor))
97+
server_protocol.makeConnection(
98+
FakeTransport(
99+
client_protocol,
100+
self.reactor,
101+
peer_address=IPv4Address("TCP", "127.0.0.1", 1234),
102+
)
103+
)
104+
105+
# the message should now get delivered
106+
self.get_success(d, by=0.1)
107+
108+
# check it arrived
109+
self.assertEqual(len(message_delivery.messages), 1)
110+
user, msg = message_delivery.messages.pop()
111+
self.assertEqual(str(user), "foo@bar.com")
112+
self.assertIn(b"Subject: test subject", msg)

0 commit comments

Comments
 (0)