summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Stanley <aeros167@gmail.com>2019-12-11 01:54:02 -0500
committerNed Deily <nad@python.org>2019-12-11 01:54:02 -0500
commitb23c0840ce07e03f2705fb08d94f8f03e5c5d5b8 (patch)
treee721d01d5160f8609fec1a7c93181ea50b114701
parent30afc91f5e70cf4748ffac77a419ba69ebca6f6a (diff)
downloadcpython-git-b23c0840ce07e03f2705fb08d94f8f03e5c5d5b8.tar.gz
[3.6] bpo-37228: Fix loop.create_datagram_endpoint()'s usage of SO_REUSEADDR (GH-17311). (GH-17571)
(cherry picked from commit ab513a38c98695f271e448fe2cb7c5e39eeaaaaf) Co-authored-by: Kyle Stanley <aeros167@gmail.com>
-rw-r--r--Doc/library/asyncio-eventloop.rst27
-rw-r--r--Lib/asyncio/base_events.py25
-rw-r--r--Lib/test/test_asyncio/test_base_events.py41
-rw-r--r--Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.rst6
4 files changed, 70 insertions, 29 deletions
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index bdba9962df..d053a60a9e 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -341,6 +341,23 @@ Creating connections
.. 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)
+ .. 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=True`` will raise an exception.
+
+ 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 same
+ socket address.
+
+ Create a datagram connection.
+
Create datagram connection: socket family :py:data:`~socket.AF_INET` or
:py:data:`~socket.AF_INET6` depending on *host* (or *family* if specified),
socket type :py:data:`~socket.SOCK_DGRAM`. *protocol_factory* must be a
@@ -365,11 +382,6 @@ Creating connections
resolution. If given, these should all be integers from the
corresponding :mod:`socket` module constants.
- * *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 Windows
@@ -393,6 +405,11 @@ Creating connections
The *family*, *proto*, *flags*, *reuse_address*, *reuse_port,
*allow_broadcast*, and *sock* parameters were added.
+ .. versionchanged:: 3.6.10
+ The *reuse_address* parameter is no longer supporter due to security
+ concerns
+
+
.. coroutinemethod:: AbstractEventLoop.create_unix_connection(protocol_factory, path, \*, ssl=None, sock=None, server_hostname=None)
Create UNIX connection: socket family :py:data:`~socket.AF_UNIX`, socket
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 48dd1fc54a..64a9fd41de 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -59,6 +59,10 @@ _HAS_IPv6 = hasattr(socket, 'AF_INET6')
# Maximum timeout passed to select to avoid OS limitations
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
+# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
+# *reuse_address* parameter
+_unset = object()
+
def _format_handle(handle):
cb = handle._callback
@@ -854,7 +858,7 @@ class BaseEventLoop(events.AbstractEventLoop):
def create_datagram_endpoint(self, protocol_factory,
local_addr=None, remote_addr=None, *,
family=0, proto=0, flags=0,
- reuse_address=None, reuse_port=None,
+ reuse_address=_unset, reuse_port=None,
allow_broadcast=None, sock=None):
"""Create datagram connection."""
if sock is not None:
@@ -863,7 +867,7 @@ class BaseEventLoop(events.AbstractEventLoop):
'A UDP Socket was expected, got {!r}'.format(sock))
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 = dict(local_addr=local_addr, remote_addr=remote_addr,
family=family, proto=proto, flags=flags,
@@ -912,8 +916,18 @@ class BaseEventLoop(events.AbstractEventLoop):
exceptions = []
- if reuse_address is None:
- reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
+ # bpo-37228
+ if reuse_address is not _unset:
+ if reuse_address:
+ raise ValueError("Passing `reuse_address=True` is no "
+ "longer supported, as the usage of "
+ "SO_REUSEPORT in UDP poses a significant "
+ "security concern.")
+ else:
+ warnings.warn("The *reuse_address* parameter has been "
+ "deprecated as of 3.6.10 and is scheduled "
+ "for removal in 3.11.", DeprecationWarning,
+ stacklevel=2)
for ((family, proto),
(local_address, remote_address)) in addr_pairs_info:
@@ -922,9 +936,6 @@ class BaseEventLoop(events.AbstractEventLoop):
try:
sock = socket.socket(
family=family, type=socket.SOCK_DGRAM, proto=proto)
- 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_asyncio/test_base_events.py
index 052a559c3c..93e8903c2c 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -1633,10 +1633,6 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
fut = self.loop.create_datagram_endpoint(
- MyDatagramProto, reuse_address=True, sock=FakeSock())
- self.assertRaises(ValueError, self.loop.run_until_complete, fut)
-
- fut = self.loop.create_datagram_endpoint(
MyDatagramProto, reuse_port=True, sock=FakeSock())
self.assertRaises(ValueError, self.loop.run_until_complete, fut)
@@ -1646,7 +1642,6 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
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.
coro = self.loop.create_datagram_endpoint(
@@ -1655,18 +1650,8 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
transport, protocol = self.loop.run_until_complete(coro)
sock = transport.get_extra_info('socket')
- reuse_address_default_on = (
- os.name == 'posix' and sys.platform != 'cygwin')
reuseport_supported = hasattr(socket, 'SO_REUSEPORT')
- 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(
@@ -1682,13 +1667,12 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
coro = self.loop.create_datagram_endpoint(
lambda: MyDatagramProto(create_future=True, loop=self.loop),
local_addr=('127.0.0.1', 0),
- reuse_address=True,
reuse_port=reuseport_supported,
allow_broadcast=True)
transport, protocol = self.loop.run_until_complete(coro)
sock = transport.get_extra_info('socket')
- self.assertTrue(
+ self.assertFalse(
sock.getsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR))
if reuseport_supported:
@@ -1703,6 +1687,29 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
self.loop.run_until_complete(protocol.done)
self.assertEqual('CLOSED', protocol.state)
+ def test_create_datagram_endpoint_reuse_address_error(self):
+ # bpo-37228: Ensure that explicit passing of `reuse_address=True`
+ # raises an error, as it is not safe to use SO_REUSEADDR when using UDP
+
+ coro = self.loop.create_datagram_endpoint(
+ lambda: MyDatagramProto(create_future=True, loop=self.loop),
+ local_addr=('127.0.0.1', 0),
+ reuse_address=True)
+
+ 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 = self.loop.create_datagram_endpoint(
+ lambda: MyDatagramProto(create_future=True, loop=self.loop),
+ local_addr=('127.0.0.1', 0),
+ reuse_address=False)
+
+ 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.rst b/Misc/NEWS.d/next/Security/2019-11-21-21-36-54.bpo-37228.yBZnFG.rst
new file mode 100644
index 0000000000..0fafb63402
--- /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`.)