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

Commit 4ca054a

Browse files
authored
Convert blacklisted IPv4 addresses to compatible IPv6 addresses. (#9240)
Also add a few more IP ranges to the default blacklist.
1 parent ff55300 commit 4ca054a

File tree

5 files changed

+160
-28
lines changed

5 files changed

+160
-28
lines changed

changelog.d/9240.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deny access to additional IP addresses by default.

docs/sample_config.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pid_file: DATADIR/homeserver.pid
169169
# - '100.64.0.0/10'
170170
# - '192.0.0.0/24'
171171
# - '169.254.0.0/16'
172+
# - '192.88.99.0/24'
172173
# - '198.18.0.0/15'
173174
# - '192.0.2.0/24'
174175
# - '198.51.100.0/24'
@@ -177,6 +178,9 @@ pid_file: DATADIR/homeserver.pid
177178
# - '::1/128'
178179
# - 'fe80::/10'
179180
# - 'fc00::/7'
181+
# - '2001:db8::/32'
182+
# - 'ff00::/8'
183+
# - 'fec0::/10'
180184

181185
# List of IP address CIDR ranges that should be allowed for federation,
182186
# identity servers, push servers, and for checking key validity for
@@ -994,6 +998,7 @@ media_store_path: "DATADIR/media_store"
994998
# - '100.64.0.0/10'
995999
# - '192.0.0.0/24'
9961000
# - '169.254.0.0/16'
1001+
# - '192.88.99.0/24'
9971002
# - '198.18.0.0/15'
9981003
# - '192.0.2.0/24'
9991004
# - '198.51.100.0/24'
@@ -1002,6 +1007,9 @@ media_store_path: "DATADIR/media_store"
10021007
# - '::1/128'
10031008
# - 'fe80::/10'
10041009
# - 'fc00::/7'
1010+
# - '2001:db8::/32'
1011+
# - 'ff00::/8'
1012+
# - 'fec0::/10'
10051013

10061014
# List of IP address CIDR ranges that the URL preview spider is allowed
10071015
# to access even if they are specified in url_preview_ip_range_blacklist.

synapse/config/repository.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
from collections import namedtuple
1818
from typing import Dict, List
1919

20-
from netaddr import IPSet
21-
22-
from synapse.config.server import DEFAULT_IP_RANGE_BLACKLIST
20+
from synapse.config.server import DEFAULT_IP_RANGE_BLACKLIST, generate_ip_set
2321
from synapse.python_dependencies import DependencyException, check_requirements
2422
from synapse.util.module_loader import load_module
2523

@@ -187,16 +185,17 @@ def read_config(self, config, **kwargs):
187185
"to work"
188186
)
189187

190-
self.url_preview_ip_range_blacklist = IPSet(
191-
config["url_preview_ip_range_blacklist"]
192-
)
193-
194188
# we always blacklist '0.0.0.0' and '::', which are supposed to be
195189
# unroutable addresses.
196-
self.url_preview_ip_range_blacklist.update(["0.0.0.0", "::"])
190+
self.url_preview_ip_range_blacklist = generate_ip_set(
191+
config["url_preview_ip_range_blacklist"],
192+
["0.0.0.0", "::"],
193+
config_path=("url_preview_ip_range_blacklist",),
194+
)
197195

198-
self.url_preview_ip_range_whitelist = IPSet(
199-
config.get("url_preview_ip_range_whitelist", ())
196+
self.url_preview_ip_range_whitelist = generate_ip_set(
197+
config.get("url_preview_ip_range_whitelist", ()),
198+
config_path=("url_preview_ip_range_whitelist",),
200199
)
201200

202201
self.url_preview_url_blacklist = config.get("url_preview_url_blacklist", ())

synapse/config/server.py

+82-17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717

18+
import itertools
1819
import logging
1920
import os.path
2021
import re
@@ -23,7 +24,7 @@
2324

2425
import attr
2526
import yaml
26-
from netaddr import IPSet
27+
from netaddr import AddrFormatError, IPNetwork, IPSet
2728

2829
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
2930
from synapse.util.stringutils import parse_and_validate_server_name
@@ -40,6 +41,66 @@
4041
# in the list.
4142
DEFAULT_BIND_ADDRESSES = ["::", "0.0.0.0"]
4243

44+
45+
def _6to4(network: IPNetwork) -> IPNetwork:
46+
"""Convert an IPv4 network into a 6to4 IPv6 network per RFC 3056."""
47+
48+
# 6to4 networks consist of:
49+
# * 2002 as the first 16 bits
50+
# * The first IPv4 address in the network hex-encoded as the next 32 bits
51+
# * The new prefix length needs to include the bits from the 2002 prefix.
52+
hex_network = hex(network.first)[2:]
53+
hex_network = ("0" * (8 - len(hex_network))) + hex_network
54+
return IPNetwork(
55+
"2002:%s:%s::/%d" % (hex_network[:4], hex_network[4:], 16 + network.prefixlen,)
56+
)
57+
58+
59+
def generate_ip_set(
60+
ip_addresses: Optional[Iterable[str]],
61+
extra_addresses: Optional[Iterable[str]] = None,
62+
config_path: Optional[Iterable[str]] = None,
63+
) -> IPSet:
64+
"""
65+
Generate an IPSet from a list of IP addresses or CIDRs.
66+
67+
Additionally, for each IPv4 network in the list of IP addresses, also
68+
includes the corresponding IPv6 networks.
69+
70+
This includes:
71+
72+
* IPv4-Compatible IPv6 Address (see RFC 4291, section 2.5.5.1)
73+
* IPv4-Mapped IPv6 Address (see RFC 4291, section 2.5.5.2)
74+
* 6to4 Address (see RFC 3056, section 2)
75+
76+
Args:
77+
ip_addresses: An iterable of IP addresses or CIDRs.
78+
extra_addresses: An iterable of IP addresses or CIDRs.
79+
config_path: The path in the configuration for error messages.
80+
81+
Returns:
82+
A new IP set.
83+
"""
84+
result = IPSet()
85+
for ip in itertools.chain(ip_addresses or (), extra_addresses or ()):
86+
try:
87+
network = IPNetwork(ip)
88+
except AddrFormatError as e:
89+
raise ConfigError(
90+
"Invalid IP range provided: %s." % (ip,), config_path
91+
) from e
92+
result.add(network)
93+
94+
# It is possible that these already exist in the set, but that's OK.
95+
if ":" not in str(network):
96+
result.add(IPNetwork(network).ipv6(ipv4_compatible=True))
97+
result.add(IPNetwork(network).ipv6(ipv4_compatible=False))
98+
result.add(_6to4(network))
99+
100+
return result
101+
102+
103+
# IP ranges that are considered private / unroutable / don't make sense.
43104
DEFAULT_IP_RANGE_BLACKLIST = [
44105
# Localhost
45106
"127.0.0.0/8",
@@ -53,6 +114,8 @@
53114
"192.0.0.0/24",
54115
# Link-local networks.
55116
"169.254.0.0/16",
117+
# Formerly used for 6to4 relay.
118+
"192.88.99.0/24",
56119
# Testing networks.
57120
"198.18.0.0/15",
58121
"192.0.2.0/24",
@@ -66,6 +129,12 @@
66129
"fe80::/10",
67130
# Unique local addresses.
68131
"fc00::/7",
132+
# Testing networks.
133+
"2001:db8::/32",
134+
# Multicast.
135+
"ff00::/8",
136+
# Site-local addresses
137+
"fec0::/10",
69138
]
70139

71140
DEFAULT_ROOM_VERSION = "6"
@@ -294,32 +363,28 @@ def read_config(self, config, **kwargs):
294363
)
295364

296365
# Attempt to create an IPSet from the given ranges
297-
try:
298-
self.ip_range_blacklist = IPSet(ip_range_blacklist)
299-
except Exception as e:
300-
raise ConfigError("Invalid range(s) provided in ip_range_blacklist.") from e
366+
301367
# Always blacklist 0.0.0.0, ::
302-
self.ip_range_blacklist.update(["0.0.0.0", "::"])
368+
self.ip_range_blacklist = generate_ip_set(
369+
ip_range_blacklist, ["0.0.0.0", "::"], config_path=("ip_range_blacklist",)
370+
)
303371

304-
try:
305-
self.ip_range_whitelist = IPSet(config.get("ip_range_whitelist", ()))
306-
except Exception as e:
307-
raise ConfigError("Invalid range(s) provided in ip_range_whitelist.") from e
372+
self.ip_range_whitelist = generate_ip_set(
373+
config.get("ip_range_whitelist", ()), config_path=("ip_range_whitelist",)
374+
)
308375

309376
# The federation_ip_range_blacklist is used for backwards-compatibility
310377
# and only applies to federation and identity servers. If it is not given,
311378
# default to ip_range_blacklist.
312379
federation_ip_range_blacklist = config.get(
313380
"federation_ip_range_blacklist", ip_range_blacklist
314381
)
315-
try:
316-
self.federation_ip_range_blacklist = IPSet(federation_ip_range_blacklist)
317-
except Exception as e:
318-
raise ConfigError(
319-
"Invalid range(s) provided in federation_ip_range_blacklist."
320-
) from e
321382
# Always blacklist 0.0.0.0, ::
322-
self.federation_ip_range_blacklist.update(["0.0.0.0", "::"])
383+
self.federation_ip_range_blacklist = generate_ip_set(
384+
federation_ip_range_blacklist,
385+
["0.0.0.0", "::"],
386+
config_path=("federation_ip_range_blacklist",),
387+
)
323388

324389
self.start_pushers = config.get("start_pushers", True)
325390

tests/config/test_server.py

+60-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
import yaml
1717

18-
from synapse.config.server import ServerConfig, is_threepid_reserved
18+
from synapse.config._base import ConfigError
19+
from synapse.config.server import ServerConfig, generate_ip_set, is_threepid_reserved
1920

2021
from tests import unittest
2122

@@ -128,3 +129,61 @@ def test_listeners_set_correctly_open_private_ports_true(self):
128129
)
129130

130131
self.assertEqual(conf["listeners"], expected_listeners)
132+
133+
134+
class GenerateIpSetTestCase(unittest.TestCase):
135+
def test_empty(self):
136+
ip_set = generate_ip_set(())
137+
self.assertFalse(ip_set)
138+
139+
ip_set = generate_ip_set((), ())
140+
self.assertFalse(ip_set)
141+
142+
def test_generate(self):
143+
"""Check adding IPv4 and IPv6 addresses."""
144+
# IPv4 address
145+
ip_set = generate_ip_set(("1.2.3.4",))
146+
self.assertEqual(len(ip_set.iter_cidrs()), 4)
147+
148+
# IPv4 CIDR
149+
ip_set = generate_ip_set(("1.2.3.4/24",))
150+
self.assertEqual(len(ip_set.iter_cidrs()), 4)
151+
152+
# IPv6 address
153+
ip_set = generate_ip_set(("2001:db8::8a2e:370:7334",))
154+
self.assertEqual(len(ip_set.iter_cidrs()), 1)
155+
156+
# IPv6 CIDR
157+
ip_set = generate_ip_set(("2001:db8::/104",))
158+
self.assertEqual(len(ip_set.iter_cidrs()), 1)
159+
160+
# The addresses can overlap OK.
161+
ip_set = generate_ip_set(("1.2.3.4", "::1.2.3.4"))
162+
self.assertEqual(len(ip_set.iter_cidrs()), 4)
163+
164+
def test_extra(self):
165+
"""Extra IP addresses are treated the same."""
166+
ip_set = generate_ip_set((), ("1.2.3.4",))
167+
self.assertEqual(len(ip_set.iter_cidrs()), 4)
168+
169+
ip_set = generate_ip_set(("1.1.1.1",), ("1.2.3.4",))
170+
self.assertEqual(len(ip_set.iter_cidrs()), 8)
171+
172+
# They can duplicate without error.
173+
ip_set = generate_ip_set(("1.2.3.4",), ("1.2.3.4",))
174+
self.assertEqual(len(ip_set.iter_cidrs()), 4)
175+
176+
def test_bad_value(self):
177+
"""An error should be raised if a bad value is passed in."""
178+
with self.assertRaises(ConfigError):
179+
generate_ip_set(("not-an-ip",))
180+
181+
with self.assertRaises(ConfigError):
182+
generate_ip_set(("1.2.3.4/128",))
183+
184+
with self.assertRaises(ConfigError):
185+
generate_ip_set((":::",))
186+
187+
# The following get treated as empty data.
188+
self.assertFalse(generate_ip_set(None))
189+
self.assertFalse(generate_ip_set({}))

0 commit comments

Comments
 (0)