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

Skip to content

Commit 95157c6

Browse files
ned-deilyaeros
andauthored
bpo-37228 10000 : Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (GH-17311) (GH-17570)
(cherry picked from commit ab513a3) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
1 parent 1b0e88d commit 95157c6

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

Doc/library/asyncio-eventloop.rst

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,21 @@ Opening network connections
429429
reuse_address=None, reuse_port=None, \
430430
allow_broadcast=None, sock=None)
431431

432+
.. note::
433+
The parameter *reuse_address* is no longer supported, as using
434+
:py:data:`~sockets.SO_REUSEADDR` poses a significant security concern for
435+
UDP. Explicitly passing ``reuse_address=True`` will raise an exception.
436+
437+
When multiple processes with differing UIDs assign sockets to an
438+
indentical UDP socket address with ``SO_REUSEADDR``, incoming packets can
439+
become randomly distributed among the sockets.
440+
441+
For supported platforms, *reuse_port* can be used as a replacement for
442+
similar functionality. With *reuse_port*,
443+
:py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
444+
prevents processes with differing UIDs from assigning sockets to the same
445+
socket address.
446+
432447
Create a datagram connection.
433448

434449
The socket family can be either :py:data:`~socket.AF_INET`,
@@ -457,11 +472,6 @@ Opening network connections
457472
resolution. If given, these should all be integers from the
458473
corresponding :mod:`socket` module constants.
459474

460-
* *reuse_address* tells the kernel to reuse a local socket in
461-
``TIME_WAIT`` state, without waiting for its natural timeout to
462-
expire. If not specified will automatically be set to ``True`` on
463-
Unix.
464-
465475
* *reuse_port* tells the kernel to allow this endpoint to be bound to the
466476
same port as other existing endpoints are bound to, so long as they all
467477
set this flag when being created. This option is not supported on Windows
@@ -485,6 +495,10 @@ Opening network connections
485495
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
486496
*allow_broadcast*, and *sock* parameters were added.
487497

498+
.. versionchanged:: 3.7.6
499+
The *reuse_address* parameter is no longer supported due to security
500+
concerns.
501+
488502
.. coroutinemethod:: loop.create_unix_connection(protocol_factory, \
489503
path=None, \*, ssl=None, sock=None, \
490504
server_hostname=None, ssl_handshake_timeout=None)

Lib/asyncio/base_events.py

Lines changed: 18 additions & 7 deletions
< 10000 td data-grid-cell-id="diff-7962e1627bc1bfbce13bef0be78d5ef7fc962f99bbc524a199ef489852ca1344-65-69-2" data-line-anchor="diff-7962e1627bc1bfbce13bef0be78d5ef7fc962f99bbc524a199ef489852ca1344R69" data-selected="false" role="gridcell" style="background-color:var(--bgColor-default);padding-right:24px" tabindex="-1" valign="top" class="focusable-grid-cell diff-text-cell right-side-diff-cell left-side">
def _format_handle(handle):
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
# Maximum timeout passed to select to avoid OS limitations
6262
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
6363

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

6569
6670
cb = handle._callback
@@ -1138,7 +1142,7 @@ async def start_tls(self, transport, protocol, sslcontext, *,
11381142
async def create_datagram_endpoint(self, protocol_factory,
11391143
local_addr=None, remote_addr=None, *,
11401144
family=0, proto=0, flags=0,
1141-
reuse_address=None, reuse_port=None,
1145+
reuse_address=_unset, reuse_port=None,
11421146
allow_broadcast=None, sock=None):
11431147
"""Create datagram connection."""
11441148
if sock is not None:
@@ -1147,7 +1151,7 @@ async def create_datagram_endpoint(self, protocol_factory,
11471151
f'A UDP Socket was expected, got {sock!r}')
11481152
if (local_addr or remote_addr or
11491153
family or proto or flags or
1150-
reuse_address or reuse_port or allow_broadcast):
1154+
reuse_port or allow_broadcast):
11511155
# show the problematic kwargs in exception msg
11521156
opts = dict(local_addr=local_addr, remote_addr=remote_addr,
11531157
family=family, proto=proto, flags=flags,
@@ -1201,8 +1205,18 @@ async def create_datagram_endpoint(self, protocol_factory,
12011205

12021206
exceptions = []
12031207

1204-
if reuse_address is None:
1205-
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
1208+
# bpo-37228
1209+
if reuse_address is not _unset:
1210+
if reuse_address:
1211+
raise ValueError("Passing `reuse_address=True` is no "
1212+
"longer supported, as the usage of "
1213+
"SO_REUSEPORT in UDP poses a significant "
1214+
"security concern.")
1215+
else:
1216+
warnings.warn("The *reuse_address* parameter has been "
1217+
"deprecated as of 3.7.6 and is scheduled "
1218+
"for removal in 3.11.", DeprecationWarning,
1219+
stacklevel=2)
12061220

12071221
for ((family, proto),
12081222
(local_address, remote_address)) in addr_pairs_info:
@@ -1211,9 +1225,6 @@ async def create_datagram_endpoint(self, protocol_factory,
12111225
try:
12121226
sock = socket.socket(
12131227
family=family, type=socket.SOCK_DGRAM, proto=proto)
1214-
if reuse_address:
1215-
sock.setsockopt(
1216-
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
12171228
if reuse_port:
12181229
_set_reuseport(sock)
12191230
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
@@ -1708,10 +1708,6 @@ class FakeSock:
17081708
MyDatagramProto, flags=1, sock=FakeSock())
17091709
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
17101710

1711-
fut = self.loop.create_datagram_endpoint(
1712-
MyDatagramProto, reuse_address=True, sock=FakeSock())
1713-
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
1714-
17151711
fut = self.loop.create_datagram_endpoint(
17161712
MyDatagramProto, reuse_port=True, sock=FakeSock())
17171713
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1722,7 +1718,6 @@ class FakeSock:
17221718

17231719
def test_create_datagram_endpoint_sockopts(self):
17241720
# Socket options should not be applied unless asked for.
1725-
# SO_REUSEADDR defaults to on for UNIX.
17261721
# SO_REUSEPORT is not available on all platforms.
17271722

17281723
coro = self.loop.create_datagram_endpoint(
@@ -1731,18 +1726,8 @@ def test_create_datagram_endpoint_sockopts(self):
17311726
transport, protocol = self.loop.run_until_complete(coro)
17321727
sock = transport.get_extra_info('socket')
17331728

1734-
reuse_address_default_on = (
1735-
os.name == 'posix' and sys.platform != 'cygwin')
17361729
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
17371730

1738-
if reuse_address_default_on:
1739-
self.assertTrue(
1740-
sock.getsockopt(
1741-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
1742-
else:
1743-
self.assertFalse(
1744-
sock.getsockopt(
1745-
socket.SOL_SOCKET, socket.SO_REUSEADDR))
17461731
if reuseport_supported:
17471732
self.assertFalse(
17481733
sock.getsockopt(
@@ -1758,13 +1743,12 @@ def test_create_datagram_endpoint_sockopts(self):
17581743
coro = self.loop.create_datagram_endpoint(
17591744
lambda: MyDatagramProto(create_future=True, loop=self.loop),
17601745
local_addr=('127.0.0.1', 0),
1761-
reuse_address=True,
17621746
reuse_port=reuseport_supported,
17631747
allow_broadcast=True)
17641748
transport, protocol = self.loop.run_until_complete(coro)
17651749
sock = transport.get_extra_info('socket')
17661750

1767-
self.assertTrue(
1751+
self.assertFalse(
17681752
sock.getsockopt(
17691753
socket.SOL_SOCKET, socket.SO_REUSEADDR))
17701754
if reuseport_supported:
@@ -1779,6 +1763,29 @@ def test_create_datagram_endpoint_sockopts(self):
17791763
self.loop.run_until_complete(protocol.done)
17801764
self.assertEqual('CLOSED', protocol.state)
17811765

1766+
def test_create_datagram_endpoint_reuse_address_error(self):
1767+
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
1768+
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
1769+
1770+
coro = self.loop.create_datagram_endpoint(
1771+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1772+
local_addr=('127.0.0.1', 0),
1773+
reuse_address=True)
1774+
1775+
with self.assertRaises(ValueError):
1776+
self.loop.run_until_complete(coro)
1777+
1778+
def test_create_datagram_endpoint_reuse_address_warning(self):
1779+
# bpo-37228: Deprecate *reuse_address* parameter
1780+
1781+
coro = self.loop.create_datagram_endpoint(
1782+
lambda: MyDatagramProto(create_future=True, loop=self.loop),
1783+
local_addr=('127.0.0.1', 0),
1784+
reuse_address=False)
1785+
1786+
with self.assertWarns(DeprecationWarning):
1787+
self.loop.run_until_complete(coro)
1788+
17821789
@patch_socket
17831790
def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
17841791
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