8000 [3.6] bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_RE… · python/cpython@b23c084 · GitHub
[go: up one dir, main page]

Skip to content

Commit b23c084

Browse files
aerosned-deily
authored andcommitted
[3.6] bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (GH-17311). (GH-17571)
(cherry picked from commit ab513a3) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
1 parent 30afc91 commit b23c084

File tree

4 files changed

+70
-29
lines changed

4 files changed

+70
-29
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,23 @@ Creating connections
341341

342342
.. coroutinemethod:: AbstractEventLoop.create_datagram_endpoint(protocol_factory, local_addr=None, remote_addr=None, \*, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None)
343343

344+
.. note::
345+
The parameter *reuse_address* is no longer supported, as using
346+
:py:data:`~sockets.SO_REUSEADDR` poses a significant security concern for
347+
UDP. Explicitly passing ``reuse_address=True`` will raise an exception.
348+
349+
When multiple processes with differing UIDs assign sockets to an
350+
indentical UDP socket address with ``SO_REUSEADDR``, incoming packets can
351+
become randomly distributed among the sockets.
352+
353+
For supported platforms, *reuse_port* can be used as a replacement for
354+
similar functionality. With *reuse_port*,
355+
:py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
356+
prevents processes with differing UIDs from assigning sockets to the same
357+
socket address.
358+
359+
Create a datagram connection.
360+
344361
Create datagram connection: socket family :py:data:`~socket.AF_INET` or
345362
:py:data:`~socket.AF_INET6` depending on *host* (or *family* if specified),
346363
socket type :py:data:`~socket.SOCK_DGRAM`. *protocol_factory* must be a
@@ -365,11 +382,6 @@ Creating connections
365382
resolution. If given, these should all be integers from the
366383
corresponding :mod:`socket` module constants.
367384

368-
* *reuse_address* tells the kernel to reuse a local socket in
369-
TIME_WAIT state, without waiting for its natural timeout to
370-
expire. If not specified will automatically be set to ``True`` on
371-
UNIX.
372-
373385
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
374386
same port as other existing endpoints are bound to, so long as they all
375387
set this flag when being created. This option is not supported on Windows
@@ -393,6 +405,11 @@ Creating connections
393405
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
394406
*allow_broadcast*, and *sock* parameters were added.
395407

408+
.. versionchanged:: 3.6.10
409+
The *reuse_address* parameter is no longer supporter due to security
410+
concerns
411+
412+
396413
.. coroutinemethod:: AbstractEventLoop.create_unix_connection(protocol_factory, path, \*, ssl=None, sock=None, server_hostname=None)
397414

398415
Create UNIX connection: socket family :py:data:`~socket.AF_UNIX`, socket

Lib/asyncio/base_events.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
# Maximum timeout passed to select to avoid OS limitations
6060
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
6161

62+
# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
63+
# *reuse_address* parameter
64+
_unset = object()
65+
6266

6367
def _format_handle(handle):
6468
cb = handle._callback
@@ -854,7 +858,7 @@ def _create_connection_transport(self, sock, protocol_factory, ssl,
854858
def create_datagram_endpoint(self, protocol_factory,
855859
local_addr=None, remote_addr=None, *,
856860
family=0, proto=0, flags=0,
857-
reuse_address=None, reuse_port=None,
861+
reuse_address=_unset, reuse_port=None,
858862
allow_broadcast=None, sock=None):
859863
"""Create datagram connection."""
860864
if sock is not None:
@@ -863,7 +867,7 @@ def create_datagram_endpoint(self, protocol_factory,
863867
'A UDP Socket was expected, got {!r}'.format(sock))
864868
if (local_addr or remote_addr or
865869
family or proto or flags or
866-
reuse_address or reuse_port or allow_broadcast):
870+
reuse_port or allow_broadcast):
867871
# show the problematic kwargs in exception msg
868872
opt 6D40 s = dict(local_addr=local_addr, remote_addr=remote_addr,
869873
family=family, proto=proto, flags=flags,
@@ -912,8 +916,18 @@ def create_datagram_endpoint(self, protocol_factory,
912916

913917
exceptions = []
914918

915-
if reuse_address is None:
916-
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
919+
# bpo-37228
920+
if reuse_address is not _unset:
921+
if reuse_address:
922+
raise ValueError("Passing `reuse_address=True` is no "
923+
"longer supported, as the usage of "
924+
"SO_REUSEPORT in UDP poses a significant "
925+
"security concern.")
926+
else:
927+
warnings.warn("The *reuse_address* parameter has been "
928+
"deprecated as of 3.6.10 and is scheduled "
929+
9E88 "for removal in 3.11.", DeprecationWarning,
930+
stacklevel=2)
917931

918932
for ((family, proto),
919933
(local_address, remote_address)) in addr_pairs_info:
@@ -922,9 +936,6 @@ def create_datagram_endpoint(self, protocol_factory,
922936
try:
923937
sock = socket.socket(
924938
family=family, type=socket.SOCK_DGRAM, proto=proto)
925-
if reuse_address:
926-
sock.setsockopt(
927-
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
928939
if reuse_port:
929940
_set_reuseport(sock)
930941
if allow_broadcast:

Lib/test/test_asyncio/test_base_events.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,10 +1632,6 @@ class FakeSock:
16321632
MyDatagramProto, flags=1, sock=FakeSock())
16331633
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
16341634

1635-
fut = self.loop.create_datagram_endpoint(
1636-
MyDatagramProto, reuse_address=True, sock=FakeSock())
1637-
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
1638-
16391635
fut = self.loop.create_datagram_endpoint(
16401636
MyDatagramProto, reuse_port=True, sock=FakeSock())
16411637
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1646,7 +1642,6 @@ class FakeSock:
16461642

16471643
def test_create_datagram_endpoint_sockopts(self):
16481644
# Socket options should not be applied unless asked for.
1649-
# SO_REUSEADDR defaults to on for UNIX.
16501645
# SO_REUSEPORT is not available on all platforms.
16511646

16521647
coro = self.loop.create_datagram_endpoint(
@@ -1655,18 +1650,8 @@ def test_create_datagram_endpoint_sockopts(self):
16551650
transport, protocol = self.loop.run_until_complete(coro)
16561651
sock = transport.get_extra_info('socket')
16571652

1658-
reuse_address_default_on = (
1659-
os.name == 'posix' and sys.platform != 'cygwin')
16601653
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
16611654

1662-
if reuse_address_default_on:
1663-
self.assertTrue(
1664-
sock.getsockopt(
1665-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
1666-
else:
1667-
self.assertFalse(
1668-
sock.getsockopt(
1669-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
16701655
if reuseport_supported:
16711656
self.assertFalse(
16721657
sock.getsockopt(
@@ -1682,13 +1667,12 @@ def test_create_datagram_endpoint_sockopts(self):
16821667
coro = self.loop.create_datagram_endpoint(
16831668
lambda: MyDatagramProto(create_future=True, loop=self.loop),
16841669
local_addr=('127.0.0.1', 0),
1685-
reuse_address=True,
16861670
reuse_port=reuseport_supported,
16871671
allow_broadcast=True)
16881672
transport, protocol = self.loop.run_until_complete(coro)
16891673
sock = transport.get_extra_info('socket')
16901674

1691-
self.assertTrue(
1675+
self.assertFalse(
16921676
sock.getsockopt(
16931677
socket.SOL_SOCKET, socket.SO_REUSEADDR))
16941678
if reuseport_supported:
@@ -1703,6 +1687,29 @@ def test_create_datagram_endpoint_sockopts(self):
17031687
self.loop.run_until_complete(protocol.done)
17041688
self.assertEqual('CLOSED', protocol.state)
17051689

1690+
def test_create_datagram_endpoint_reuse_address_error(self):
1691+
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
1692+
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
1693+
1694+
coro = self.loop.create_datagram_endpoint(
1695+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1696+
local_addr=('127.0.0.1', 0),
1697+
reuse_address=True)
1698+
1699+
with self.assertRaises(ValueError):
1700+
self.loop.run_until_complete(coro)
1701+
1702+
def test_create_datagram_endpoint_reuse_address_warning(self):
1703+
# bpo-37228: Deprecate *reuse_address* parameter
1704+
1705+
coro = self.loop.create_datagram_endpoint(
1706+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1707+
local_addr=('127.0.0.1', 0),
1708+
reuse_address=False)
1709+
1710+
with self.assertWarns(DeprecationWarning):
1711+
self.loop.run_until_complete(coro)
1712+
17061713
@patch_socket
17071714
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
17081715
del m_socket.SO_REUSEPORT
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Due to significant security concerns, the *reuse_address* parameter of
2+
:meth:`asyncio.loop.create_datagram_endpoint` is no longer supported. This is
3+
because of the behavior of ``SO_REUSEADDR`` in UDP. For more details, see the
4+
documentation for ``loop.create_datagram_endpoint()``.
5+
(Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in
6+
:issue:`37228`.)

0 commit comments

Comments
 (0)
0