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

Commit 652a6b0

Browse files
committed
Merge branch 'master' into develop
2 parents dc6366a + d1473f7 commit 652a6b0

File tree

10 files changed

+319
-70
lines changed

10 files changed

+319
-70
lines changed

CHANGES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
Synapse 1.33.2 (2021-05-11)
2+
===========================
3+
4+
Due to the security issue 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+
This release fixes a denial of service attack ([CVE-2021-29471](https://github.com/matrix-org/synapse/security/advisories/GHSA-x345-32rc-8h85)) against Synapse's push rules implementation. Server admins are encouraged to upgrade.
10+
11+
Internal Changes
12+
----------------
13+
14+
- Unpin attrs dependency. ([\#9946](https://github.com/matrix-org/synapse/issues/9946))
15+
16+
117
Synapse 1.33.1 (2021-05-06)
218
===========================
319

changelog.d/9946.misc

Lines changed: 0 additions & 1 deletion
This file was deleted.

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.33.2) stable; urgency=medium
2+
3+
* New synapse release 1.33.2.
4+
5+
-- Synapse Packaging team <[email protected]> Tue, 11 May 2021 11:17:59 +0100
6+
17
matrix-synapse-py3 (1.33.1) stable; urgency=medium
28

39
* New synapse release 1.33.1.

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.33.1"
50+
__version__ = "1.33.2"
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/config/tls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import warnings
1818
from datetime import datetime
1919
from hashlib import sha256
20-
from typing import List, Optional
20+
from typing import List, Optional, Pattern
2121

2222
from unpaddedbase64 import encode_base64
2323

@@ -124,7 +124,7 @@ def read_config(self, config: dict, config_dir_path: str, **kwargs):
124124
fed_whitelist_entries = []
125125

126126
# Support globs (*) in whitelist values
127-
self.federation_certificate_verification_whitelist = [] # type: List[str]
127+
self.federation_certificate_verification_whitelist = [] # type: List[Pattern]
128128
for entry in fed_whitelist_entries:
129129
try:
130130
entry_regex = glob_to_regex(entry.encode("ascii").decode("ascii"))

synapse/push/push_rule_evaluator.py

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from synapse.events import EventBase
2121
from synapse.types import UserID
22+
from synapse.util import glob_to_regex, re_word_boundary
2223
from synapse.util.caches.lrucache import LruCache
2324

2425
logger = logging.getLogger(__name__)
@@ -183,7 +184,7 @@ def _contains_display_name(self, display_name: str) -> bool:
183184
r = regex_cache.get((display_name, False, True), None)
184185
if not r:
185186
r1 = re.escape(display_name)
186-
r1 = _re_word_boundary(r1)
187+
r1 = re_word_boundary(r1)
187188
r = re.compile(r1, flags=re.IGNORECASE)
188189
regex_cache[(display_name, False, True)] = r
189190

@@ -212,64 +213,14 @@ def _glob_matches(glob: str, value: str, word_boundary: bool = False) -> bool:
212213
try:
213214
r = regex_cache.get((glob, True, word_boundary), None)
214215
if not r:
215-
r = _glob_to_re(glob, word_boundary)
216+
r = glob_to_regex(glob, word_boundary)
216217
regex_cache[(glob, True, word_boundary)] = r
217218
return bool(r.search(value))
218219
except re.error:
219220
logger.warning("Failed to parse glob to regex: %r", glob)
220221
return False
221222

222223

223-
def _glob_to_re(glob: str, word_boundary: bool) -> Pattern:
224-
"""Generates regex for a given glob.
225-
226-
Args:
227-
glob
228-
word_boundary: Whether to match against word boundaries or entire string.
229-
"""
230-
if IS_GLOB.search(glob):
231-
r = re.escape(glob)
232-
233-
r = r.replace(r"\*", ".*?")
234-
r = r.replace(r"\?", ".")
235-
236-
# handle [abc], [a-z] and [!a-z] style ranges.
237-
r = GLOB_REGEX.sub(
238-
lambda x: (
239-
"[%s%s]" % (x.group(1) and "^" or "", x.group(2).replace(r"\\\-", "-"))
240-
),
241-
r,
242-
)
243-
if word_boundary:
244-
r = _re_word_boundary(r)
245-
246-
return re.compile(r, flags=re.IGNORECASE)
247-
else:
248-
r = "^" + r + "$"
249-
250-
return re.compile(r, flags=re.IGNORECASE)
251-
elif word_boundary:
252-
r = re.escape(glob)
253-
r = _re_word_boundary(r)
254-
255-
return re.compile(r, flags=re.IGNORECASE)
256-
else:
257-
r = "^" + re.escape(glob) + "$"
258-
return re.compile(r, flags=re.IGNORECASE)
259-
260-
261-
def _re_word_boundary(r: str) -> str:
262-
"""
263-
Adds word boundary characters to the start and end of an
264-
expression to require that the match occur as a whole word,
265-
but do so respecting the fact that strings starting or ending
266-
with non-word characters will change word boundaries.
267-
"""
268-
# we can't use \b as it chokes on unicode. however \W seems to be okay
269-
# as shorthand for [^0-9A-Za-z_].
270-
return r"(^|\W)%s(\W|$)" % (r,)
271-
272-
273224
def _flatten_dict(
274225
d: Union[EventBase, dict],
275226
prefix: Optional[List[str]] = None,

synapse/util/__init__.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import json
1616
import logging
1717
import re
18+
from typing import Pattern
1819

1920
import attr
2021
from frozendict import frozendict
@@ -26,6 +27,9 @@
2627
logger = logging.getLogger(__name__)
2728

2829

30+
_WILDCARD_RUN = re.compile(r"([\?\*]+)")
31+
32+
2933
def _reject_invalid_json(val):
3034
"""Do not allow Infinity, -Infinity, or NaN values in JSON."""
3135
raise ValueError("Invalid JSON value: '%s'" % val)
@@ -158,25 +162,54 @@ def log_failure(failure, msg, consumeErrors=True):
158162
return failure
159163

160164

161-
def glob_to_regex(glob):
165+
def glob_to_regex(glob: str, word_boundary: bool = False) -> Pattern:
162166
"""Converts a glob to a compiled regex object.
163167
164-
The regex is anchored at the beginning and end of the string.
165-
166168
Args:
167-
glob (str)
169+
glob: pattern to match
170+
word_boundary: If True, the pattern will be allowed to match at word boundaries
171+
anywhere in the string. Otherwise, the pattern is anchored at the start and
172+
end of the string.
168173
169174
Returns:
170-
re.RegexObject
175+
compiled regex pattern
171176
"""
172-
res = ""
173-
for c in glob:
174-
if c == "*":
175-
res = res + ".*"
176-
elif c == "?":
177-
res = res + "."
177+
178+
# Patterns with wildcards must be simplified to avoid performance cliffs
179+
# - The glob `?**?**?` is equivalent to the glob `???*`
180+
# - The glob `???*` is equivalent to the regex `.{3,}`
181+
chunks = []
182+
for chunk in _WILDCARD_RUN.split(glob):
183+
# No wildcards? re.escape()
184+
if not _WILDCARD_RUN.match(chunk):
185+
chunks.append(re.escape(chunk))
186+
continue
187+
188+
# Wildcards? Simplify.
189+
qmarks = chunk.count("?")
190+
if "*" in chunk:
191+
chunks.append(".{%d,}" % qmarks)
178192
else:
179-
res = res + re.escape(c)
193+
chunks.append(".{%d}" % qmarks)
194+
195+
res = "".join(chunks)
180196

181-
# \A anchors at start of string, \Z at end of string
182-
return re.compile(r"\A" + res + r"\Z", re.IGNORECASE)
197+
if word_boundary:
198+
res = re_word_boundary(res)
199+
else:
200+
# \A anchors at start of string, \Z at end of string
201+
res = r"\A" + res + r"\Z"
202+
203+
return re.compile(res, re.IGNORECASE)
204+
205+
206+
def re_word_boundary(r: str) -> str:
207+
"""
208+
Adds word boundary characters to the start and end of an
209+
expression to require that the match occur as a whole word,
210+
but do so respecting the fact that strings starting or ending
211+
with non-word characters will change word boundaries.
212+
"""
213+
# we can't use \b as it chokes on unicode. however \W seems to be okay
214+
# as shorthand for [^0-9A-Za-z_].
215+
return r"(^|\W)%s(\W|$)" % (r,)

tests/federation/test_federation_server.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ def test_block_ip_literals(self):
7474
self.assertFalse(server_matches_acl_event("[1:2::]", e))
7575
self.assertTrue(server_matches_acl_event("1:2:3:4", e))
7676

77+
def test_wildcard_matching(self):
78+
e = _create_acl_event({"allow": ["good*.com"]})
79+
self.assertTrue(
80+
server_matches_acl_event("good.com", e),
81+
"* matches 0 characters",
82+
)
83+
self.assertTrue(
84+
server_matches_acl_event("GOOD.COM", e),
85+
"pattern is case-insensitive",
86+
)
87+
self.assertTrue(
88+
server_matches_acl_event("good.aa.com", e),
89+
"* matches several characters, including '.'",
90+
)
91+
self.assertFalse(
92+
server_matches_acl_event("ishgood.com", e),
93+
"pattern does not allow prefixes",
94+
)
95+
7796

7897
class StateQueryTests(unittest.FederatingHomeserverTestCase):
7998

0 commit comments

Comments
 (0)