Skip to content

Commit

Permalink
Merge tag 'v1.41.1' into 2021-05
Browse files Browse the repository at this point in the history
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))
  • Loading branch information
aaronraimist committed Aug 31, 2021
2 parents e1fe53a + a4c8a2f commit 8bcbeb4
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 28 deletions.
32 changes: 32 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
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](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.**

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 [52c7a51cf](https://github.com/matrix-org/synapse/commit/52c7a51cf).

- **[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.**

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 [cb35df940a](https://github.com/matrix-org/synapse/commit/cb35df940a), [\#10723](https://github.com/matrix-org/synapse/issues/10723).

Bugfixes
--------

- 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))


Synapse 1.41.0 (2021-08-24)
===========================

Expand Down
1 change: 1 addition & 0 deletions changelog.d/10713.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a regression introduced in Synapse 1.41 which broke email transmission on Systems using older versions of the Twisted library.
1 change: 1 addition & 0 deletions changelog.d/10723.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix unauthorised exposure of room metadata to communities.
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
matrix-synapse-py3 (1.41.1) stable; urgency=high

* New synapse release 1.41.1.

-- Synapse Packaging team <packages@matrix.org> Tue, 31 Aug 2021 12:59:10 +0100

matrix-synapse-py3 (1.41.0) stable; urgency=medium

* New synapse release 1.41.0.
Expand Down
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ files =
tests/test_utils,
tests/handlers/test_password_providers.py,
tests/handlers/test_room_summary.py,
tests/handlers/test_send_email.py,
tests/rest/client/v1/test_login.py,
tests/rest/client/v2_alpha/test_auth.py,
tests/util/test_itertools.py,
Expand Down
2 changes: 1 addition & 1 deletion synapse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
except ImportError:
pass

__version__ = "1.41.0"
__version__ = "1.41.1"

if bool(os.environ.get("SYNAPSE_TEST_PATCH_LOG_CONTEXTS", False)):
# We import here so that we don't have to install a bunch of deps when
Expand Down
18 changes: 16 additions & 2 deletions synapse/groups/groups_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ async def get_rooms_in_group(
requester_user_id, group_id
)

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

joined_users = await self.store.get_users_in_room(room_id)

# check the user is actually allowed to see the room before showing it to them
allow_private = requester_user_id in joined_users

entry = await self.room_list_handler.generate_room_entry(
room_id, len(joined_users), with_alias=False, allow_private=True
room_id,
len(joined_users),
with_alias=False,
allow_private=allow_private,
)

if not entry:
Expand All @@ -354,7 +368,7 @@ async def get_rooms_in_group(

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

return {"chunk": chunk, "total_room_count_estimate": len(room_results)}
return {"chunk": chunk, "total_room_count_estimate": len(chunk)}


class GroupsServerHandler(GroupsServerWorkerHandler):
Expand Down
23 changes: 20 additions & 3 deletions synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,37 @@ async def get_state_events(

if not last_events:
raise NotFoundError("Can't find event for token %s" % (at_token,))
last_event = last_events[0]

# check whether the user is in the room at that time to determine
# whether they should be treated as peeking.
state_map = await self.state_store.get_state_for_event(
last_event.event_id,
StateFilter.from_types([(EventTypes.Member, user_id)]),
)

joined = False
membership_event = state_map.get((EventTypes.Member, user_id))
if membership_event:
joined = membership_event.membership == Membership.JOIN

is_peeking = not joined

visible_events = await filter_events_for_client(
self.storage,
user_id,
last_events,
filter_send_to_client=False,
is_peeking=is_peeking,
)

event = last_events[0]
if visible_events:
room_state_events = await self.state_store.get_state_for_events(
[event.event_id], state_filter=state_filter
[last_event.event_id], state_filter=state_filter
)
room_state: Mapping[Any, EventBase] = room_state_events[event.event_id]
room_state: Mapping[Any, EventBase] = room_state_events[
last_event.event_id
]
else:
raise AuthError(
403,
Expand Down
65 changes: 47 additions & 18 deletions synapse/handlers/send_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
from io import BytesIO
from typing import TYPE_CHECKING, Optional

from pkg_resources import parse_version

import twisted
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IReactorTCP
from twisted.mail.smtp import ESMTPSenderFactory
from twisted.internet.interfaces import IOpenSSLContextFactory, IReactorTCP
from twisted.mail.smtp import ESMTPSender, ESMTPSenderFactory

from synapse.logging.context import make_deferred_yieldable

Expand All @@ -30,6 +33,19 @@

logger = logging.getLogger(__name__)

_is_old_twisted = parse_version(twisted.__version__) < parse_version("21")


class _NoTLSESMTPSender(ESMTPSender):
"""Extend ESMTPSender to disable TLS
Unfortunately, before Twisted 21.2, ESMTPSender doesn't give an easy way to disable
TLS, so we override its internal method which it uses to generate a context factory.
"""

def _getContextFactory(self) -> Optional[IOpenSSLContextFactory]:
return None


async def _sendmail(
reactor: IReactorTCP,
Expand All @@ -42,7 +58,7 @@ async def _sendmail(
password: Optional[bytes] = None,
require_auth: bool = False,
require_tls: bool = False,
tls_hostname: Optional[str] = None,
enable_tls: bool = True,
) -> None:
"""A simple wrapper around ESMTPSenderFactory, to allow substitution in tests
Expand All @@ -57,24 +73,37 @@ async def _sendmail(
password: password to give when authenticating
require_auth: if auth is not offered, fail the request
require_tls: if TLS is not offered, fail the reqest
tls_hostname: TLS hostname to check for. None to disable TLS.
enable_tls: True to enable TLS. If this is False and require_tls is True,
the request will fail.
"""
msg = BytesIO(msg_bytes)

d: "Deferred[object]" = Deferred()

factory = ESMTPSenderFactory(
username,
password,
from_addr,
to_addr,
msg,
d,
heloFallback=True,
requireAuthentication=require_auth,
requireTransportSecurity=require_tls,
hostname=tls_hostname,
)
def build_sender_factory(**kwargs) -> ESMTPSenderFactory:
return ESMTPSenderFactory(
username,
password,
from_addr,
to_addr,
msg,
d,
heloFallback=True,
requireAuthentication=require_auth,
requireTransportSecurity=require_tls,
**kwargs,
)

if _is_old_twisted:
# before twisted 21.2, we have to override the ESMTPSender protocol to disable
# TLS
factory = build_sender_factory()

if not enable_tls:
factory.protocol = _NoTLSESMTPSender
else:
# for twisted 21.2 and later, there is a 'hostname' parameter which we should
# set to enable TLS.
factory = build_sender_factory(hostname=smtphost if enable_tls else None)

# the IReactorTCP interface claims host has to be a bytes, which seems to be wrong
reactor.connectTCP(smtphost, smtpport, factory, timeout=30, bindAddress=None) # type: ignore[arg-type]
Expand Down Expand Up @@ -154,5 +183,5 @@ async def send_email(
password=self._smtp_pass,
require_auth=self._smtp_user is not None,
require_tls=self._require_transport_security,
tls_hostname=self._smtp_host if self._enable_tls else None,
enable_tls=self._enable_tls,
)
112 changes: 112 additions & 0 deletions tests/handlers/test_send_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from typing import List, Tuple

from zope.interface import implementer

from twisted.internet import defer
from twisted.internet.address import IPv4Address
from twisted.internet.defer import ensureDeferred
from twisted.mail import interfaces, smtp

from tests.server import FakeTransport
from tests.unittest import HomeserverTestCase


@implementer(interfaces.IMessageDelivery)
class _DummyMessageDelivery:
def __init__(self):
# (recipient, message) tuples
self.messages: List[Tuple[smtp.Address, bytes]] = []

def receivedHeader(self, helo, origin, recipients):
return None

def validateFrom(self, helo, origin):
return origin

def record_message(self, recipient: smtp.Address, message: bytes):
self.messages.append((recipient, message))

def validateTo(self, user: smtp.User):
return lambda: _DummyMessage(self, user)


@implementer(interfaces.IMessageSMTP)
class _DummyMessage:
"""IMessageSMTP implementation which saves the message delivered to it
to the _DummyMessageDelivery object.
"""

def __init__(self, delivery: _DummyMessageDelivery, user: smtp.User):
self._delivery = delivery
self._user = user
self._buffer: List[bytes] = []

def lineReceived(self, line):
self._buffer.append(line)

def eomReceived(self):
message = b"\n".join(self._buffer) + b"\n"
self._delivery.record_message(self._user.dest, message)
return defer.succeed(b"saved")

def connectionLost(self):
pass


class SendEmailHandlerTestCase(HomeserverTestCase):
def test_send_email(self):
"""Happy-path test that we can send email to a non-TLS server."""
h = self.hs.get_send_email_handler()
d = ensureDeferred(
h.send_email(
"foo@bar.com", "test subject", "Tests", "HTML content", "Text content"
)
)
# there should be an attempt to connect to localhost:25
self.assertEqual(len(self.reactor.tcpClients), 1)
(host, port, client_factory, _timeout, _bindAddress) = self.reactor.tcpClients[
0
]
self.assertEqual(host, "localhost")
self.assertEqual(port, 25)

# wire it up to an SMTP server
message_delivery = _DummyMessageDelivery()
server_protocol = smtp.ESMTP()
server_protocol.delivery = message_delivery
# make sure that the server uses the test reactor to set timeouts
server_protocol.callLater = self.reactor.callLater # type: ignore[assignment]

client_protocol = client_factory.buildProtocol(None)
client_protocol.makeConnection(FakeTransport(server_protocol, self.reactor))
server_protocol.makeConnection(
FakeTransport(
client_protocol,
self.reactor,
peer_address=IPv4Address("TCP", "127.0.0.1", 1234),
)
)

# the message should now get delivered
self.get_success(d, by=0.1)

# check it arrived
self.assertEqual(len(message_delivery.messages), 1)
user, msg = message_delivery.messages.pop()
self.assertEqual(str(user), "foo@bar.com")
self.assertIn(b"Subject: test subject", msg)
Loading

0 comments on commit 8bcbeb4

Please sign in to comment.