[Python-checkins] (no subject)

Łukasz Langa webhook-mailer at python.org
Mon Dec 9 09:39:59 EST 2019




To: python-checkins at python.org
Subject: bpo-37228: Fix loop.create_datagram_endpoint()'s usage of
 SO_REUSEADDR (GH-17311) (#17529)
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0

https://github.com/python/cpython/commit/79c29742a8ba96fc933efe34e55e90537b3e=
b780
commit: 79c29742a8ba96fc933efe34e55e90537b3eb780
branch: 3.8
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.co=
m>
committer: =C5=81ukasz Langa <lukasz at langa.pl>
date: 2019-12-09T15:39:54+01:00
summary:

bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (GH-17=
311) (#17529)

(cherry picked from commit ab513a38c98695f271e448fe2cb7c5e39eeaaaaf)

Co-authored-by: Kyle Stanley <aeros167 at gmail.com>

files:
A Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.rst
M Doc/library/asyncio-eventloop.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_base_events.py

diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloo=
p.rst
index 1f4f0b6e75b9e..ecd69fd669f1a 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -461,6 +461,21 @@ Opening network connections
                         reuse_address=3DNone, reuse_port=3DNone, \
                         allow_broadcast=3DNone, sock=3DNone)
=20
+   .. note::
+      The parameter *reuse_address* is no longer supported, as using
+      :py:data:`~sockets.SO_REUSEADDR` poses a significant security concern =
for
+      UDP. Explicitly passing ``reuse_address=3DTrue`` will raise an excepti=
on.
+
+      When multiple processes with differing UIDs assign sockets to an
+      indentical UDP socket address with ``SO_REUSEADDR``, incoming packets =
can
+      become randomly distributed among the sockets.
+
+      For supported platforms, *reuse_port* can be used as a replacement for
+      similar functionality. With *reuse_port*,
+      :py:data:`~sockets.SO_REUSEPORT` is used instead, which specifically
+      prevents processes with differing UIDs from assigning sockets to the s=
ame
+      socket address.
+
    Create a datagram connection.
=20
    The socket family can be either :py:data:`~socket.AF_INET`,
@@ -489,11 +504,6 @@ Opening network connections
      resolution. If given, these should all be integers from the
      corresponding :mod:`socket` module constants.
=20
-   * *reuse_address* tells the kernel to reuse a local socket in
-     ``TIME_WAIT`` state, without waiting for its natural timeout to
-     expire. If not specified will automatically be set to ``True`` on
-     Unix.
-
    * *reuse_port* tells the kernel to allow this endpoint to be bound to the
      same port as other existing endpoints are bound to, so long as they all
      set this flag when being created. This option is not supported on Windo=
ws
@@ -515,6 +525,10 @@ Opening network connections
       The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
       *allow_broadcast*, and *sock* parameters were added.
=20
+   .. versionchanged:: 3.8.1
+      The *reuse_address* parameter is no longer supported due to security
+      concerns.
+
    .. versionchanged:: 3.8
       Added support for Windows.
=20
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 14b80bdda9c03..bfd40115bed38 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -66,6 +66,10 @@
 # Maximum timeout passed to select to avoid OS limitations
 MAXIMUM_SELECT_TIMEOUT =3D 24 * 3600
=20
+# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
+# *reuse_address* parameter
+_unset =3D object()
+
=20
 def _format_handle(handle):
     cb =3D handle._callback
@@ -1201,7 +1205,7 @@ def _check_sendfile_params(self, sock, file, offset, co=
unt):
     async def create_datagram_endpoint(self, protocol_factory,
                                        local_addr=3DNone, remote_addr=3DNone=
, *,
                                        family=3D0, proto=3D0, flags=3D0,
-                                       reuse_address=3DNone, reuse_port=3DNo=
ne,
+                                       reuse_address=3D_unset, reuse_port=3D=
None,
                                        allow_broadcast=3DNone, sock=3DNone):
         """Create datagram connection."""
         if sock is not None:
@@ -1210,7 +1214,7 @@ def _check_sendfile_params(self, sock, file, offset, co=
unt):
                     f'A UDP Socket was expected, got {sock!r}')
             if (local_addr or remote_addr or
                     family or proto or flags or
-                    reuse_address or reuse_port or allow_broadcast):
+                    reuse_port or allow_broadcast):
                 # show the problematic kwargs in exception msg
                 opts =3D dict(local_addr=3Dlocal_addr, remote_addr=3Dremote_=
addr,
                             family=3Dfamily, proto=3Dproto, flags=3Dflags,
@@ -1277,8 +1281,18 @@ def _check_sendfile_params(self, sock, file, offset, c=
ount):
=20
             exceptions =3D []
=20
-            if reuse_address is None:
-                reuse_address =3D os.name =3D=3D 'posix' and sys.platform !=
=3D 'cygwin'
+            # bpo-37228
+            if reuse_address is not _unset:
+                if reuse_address:
+                    raise ValueError("Passing `reuse_address=3DTrue` is no "
+                                     "longer supported, as the usage of "
+                                     "SO_REUSEPORT in UDP poses a significan=
t "
+                                     "security concern.")
+                else:
+                    warnings.warn("The *reuse_address* parameter has been "
+                                  "deprecated as of 3.5.10 and is scheduled "
+                                  "for removal in 3.11.", DeprecationWarning,
+                                  stacklevel=3D2)
=20
             for ((family, proto),
                  (local_address, remote_address)) in addr_pairs_info:
@@ -1287,9 +1301,6 @@ def _check_sendfile_params(self, sock, file, offset, co=
unt):
                 try:
                     sock =3D socket.socket(
                         family=3Dfamily, type=3Dsocket.SOCK_DGRAM, proto=3Dp=
roto)
-                    if reuse_address:
-                        sock.setsockopt(
-                            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                     if reuse_port:
                         _set_reuseport(sock)
                     if allow_broadcast:
diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asynci=
o/test_base_events.py
index 92fdaf17e2011..3a23059911211 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -1732,10 +1732,6 @@ class FakeSock:
             MyDatagramProto, flags=3D1, sock=3DFakeSock())
         self.assertRaises(ValueError, self.loop.run_until_complete, fut)
=20
-        fut =3D self.loop.create_datagram_endpoint(
-            MyDatagramProto, reuse_address=3DTrue, sock=3DFakeSock())
-        self.assertRaises(ValueError, self.loop.run_until_complete, fut)
-
         fut =3D self.loop.create_datagram_endpoint(
             MyDatagramProto, reuse_port=3DTrue, sock=3DFakeSock())
         self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1746,7 +1742,6 @@ class FakeSock:
=20
     def test_create_datagram_endpoint_sockopts(self):
         # Socket options should not be applied unless asked for.
-        # SO_REUSEADDR defaults to on for UNIX.
         # SO_REUSEPORT is not available on all platforms.
=20
         coro =3D self.loop.create_datagram_endpoint(
@@ -1755,18 +1750,8 @@ def test_create_datagram_endpoint_sockopts(self):
         transport, protocol =3D self.loop.run_until_complete(coro)
         sock =3D transport.get_extra_info('socket')
=20
-        reuse_address_default_on =3D (
-            os.name =3D=3D 'posix' and sys.platform !=3D 'cygwin')
         reuseport_supported =3D hasattr(socket, 'SO_REUSEPORT')
=20
-        if reuse_address_default_on:
-            self.assertTrue(
-                sock.getsockopt(
-                    socket.SOL_SOCKET, socket.SO_REUSEADDR))
-        else:
-            self.assertFalse(
-                sock.getsockopt(
-                    socket.SOL_SOCKET, socket.SO_REUSEADDR))
         if reuseport_supported:
             self.assertFalse(
                 sock.getsockopt(
@@ -1782,13 +1767,12 @@ def test_create_datagram_endpoint_sockopts(self):
         coro =3D self.loop.create_datagram_endpoint(
             lambda: MyDatagramProto(create_future=3DTrue, loop=3Dself.loop),
             local_addr=3D('127.0.0.1', 0),
-            reuse_address=3DTrue,
             reuse_port=3Dreuseport_supported,
             allow_broadcast=3DTrue)
         transport, protocol =3D self.loop.run_until_complete(coro)
         sock =3D transport.get_extra_info('socket')
=20
-        self.assertTrue(
+        self.assertFalse(
             sock.getsockopt(
                 socket.SOL_SOCKET, socket.SO_REUSEADDR))
         if reuseport_supported:
@@ -1803,6 +1787,29 @@ def test_create_datagram_endpoint_sockopts(self):
         self.loop.run_until_complete(protocol.done)
         self.assertEqual('CLOSED', protocol.state)
=20
+    def test_create_datagram_endpoint_reuse_address_error(self):
+        # bpo-37228: Ensure that explicit passing of `reuse_address=3DTrue`
+        # raises an error, as it is not safe to use SO_REUSEADDR when using =
UDP
+
+        coro =3D self.loop.create_datagram_endpoint(
+            lambda: MyDatagramProto(create_future=3DTrue, loop=3Dself.loop),
+            local_addr=3D('127.0.0.1', 0),
+            reuse_address=3DTrue)
+
+        with self.assertRaises(ValueError):
+            self.loop.run_until_complete(coro)
+
+    def test_create_datagram_endpoint_reuse_address_warning(self):
+        # bpo-37228: Deprecate *reuse_address* parameter
+
+        coro =3D self.loop.create_datagram_endpoint(
+            lambda: MyDatagramProto(create_future=3DTrue, loop=3Dself.loop),
+            local_addr=3D('127.0.0.1', 0),
+            reuse_address=3DFalse)
+
+        with self.assertWarns(DeprecationWarning):
+            self.loop.run_until_complete(coro)
+
     @patch_socket
     def test_create_datagram_endpoint_nosoreuseport(self, m_socket):
         del m_socket.SO_REUSEPORT
diff --git a/Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.r=
st b/Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.rst
new file mode 100644
index 0000000000000..0fafb63402e46
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.rst
@@ -0,0 +1,6 @@
+Due to significant security concerns, the *reuse_address* parameter of
+:meth:`asyncio.loop.create_datagram_endpoint` is no longer supported. This is
+because of the behavior of ``SO_REUSEADDR`` in UDP. For more details, see the
+documentation for ``loop.create_datagram_endpoint()``.
+(Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in
+:issue:`37228`.)



More information about the Python-checkins mailing list