Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 78df104

Browse filesBrowse files
sethmlarsongpshead
andauthored
gh-122133: Authenticate socket connection for socket.socketpair() fallback (GH-122134)
* Authenticate socket connection for `socket.socketpair()` fallback when the platform does not have a native `socketpair` C API. We authenticate in-process using `getsocketname` and `getpeername` (thanks to Nathaniel J Smith for that suggestion). Co-authored-by: Gregory P. Smith <greg@krypto.org>
1 parent 76bdfa4 commit 78df104
Copy full SHA for 78df104

File tree

3 files changed

+147
-3
lines changed
Filter options

3 files changed

+147
-3
lines changed

‎Lib/socket.py

Copy file name to clipboardExpand all lines: Lib/socket.py
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,23 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
650650
raise
651651
finally:
652652
lsock.close()
653+
654+
# Authenticating avoids using a connection from something else
655+
# able to connect to {host}:{port} instead of us.
656+
# We expect only AF_INET and AF_INET6 families.
657+
try:
658+
if (
659+
ssock.getsockname() != csock.getpeername()
660+
or csock.getsockname() != ssock.getpeername()
661+
):
662+
raise ConnectionError("Unexpected peer connection")
663+
except:
664+
# getsockname() and getpeername() can fail
665+
# if either socket isn't connected.
666+
ssock.close()
667+
csock.close()
668+
raise
669+
653670
return (ssock, csock)
654671
__all__.append("socketpair")
655672

‎Lib/test/test_socket.py

Copy file name to clipboardExpand all lines: Lib/test/test_socket.py
+125-3Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -592,19 +592,27 @@ class SocketPairTest(unittest.TestCase, ThreadableTest):
592592
def __init__(self, methodName='runTest'):
593593
unittest.TestCase.__init__(self, methodName=methodName)
594594
ThreadableTest.__init__(self)
595+
self.cli = None
596+
self.serv = None
597+
598+
def socketpair(self):
599+
# To be overridden by some child classes.
600+
return socket.socketpair()
595601

596602
def setUp(self):
597-
self.serv, self.cli = socket.socketpair()
603+
self.serv, self.cli = self.socketpair()
598604

599605
def tearDown(self):
600-
self.serv.close()
606+
if self.serv:
607+
self.serv.close()
601608
self.serv = None
602609

603610
def clientSetUp(self):
604611
pass
605612

606613
def clientTearDown(self):
607-
self.cli.close()
614+
if self.cli:
615+
self.cli.close()
608616
self.cli = None
609617
ThreadableTest.clientTearDown(self)
610618

@@ -4852,6 +4860,120 @@ def _testSend(self):
48524860
self.assertEqual(msg, MSG)
48534861

48544862

4863+
class PurePythonSocketPairTest(SocketPairTest):
4864+
4865+
# Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the
4866+
# code path we're using regardless platform is the pure python one where
4867+
# `_socket.socketpair` does not exist. (AF_INET does not work with
4868+
# _socket.socketpair on many platforms).
4869+
def socketpair(self):
4870+
# called by super().setUp().
4871+
try:
4872+
return socket.socketpair(socket.AF_INET6)
4873+
except OSError:
4874+
return socket.socketpair(socket.AF_INET)
4875+
4876+
# Local imports in this class make for easy security fix backporting.
4877+
4878+
def setUp(self):
4879+
import _socket
4880+
self._orig_sp = getattr(_socket, 'socketpair', None)
4881+
if self._orig_sp is not None:
4882+
# This forces the version using the non-OS provided socketpair
4883+
# emulation via an AF_INET socket in Lib/socket.py.
4884+
del _socket.socketpair
4885+
import importlib
4886+
global socket
4887+
socket = importlib.reload(socket)
4888+
else:
4889+
pass # This platform already uses the non-OS provided version.
4890+
super().setUp()
4891+
4892+
def tearDown(self):
4893+
super().tearDown()
4894+
import _socket
4895+
if self._orig_sp is not None:
4896+
# Restore the default socket.socketpair definition.
4897+
_socket.socketpair = self._orig_sp
4898+
import importlib
4899+
global socket
4900+
socket = importlib.reload(socket)
4901+
4902+
def test_recv(self):
4903+
msg = self.serv.recv(1024)
4904+
self.assertEqual(msg, MSG)
4905+
4906+
def _test_recv(self):
4907+
self.cli.send(MSG)
4908+
4909+
def test_send(self):
4910+
self.serv.send(MSG)
4911+
4912+
def _test_send(self):
4913+
msg = self.cli.recv(1024)
4914+
self.assertEqual(msg, MSG)
4915+
4916+
def test_ipv4(self):
4917+
cli, srv = socket.socketpair(socket.AF_INET)
4918+
cli.close()
4919+
srv.close()
4920+
4921+
def _test_ipv4(self):
4922+
pass
4923+
4924+
@unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or
4925+
not hasattr(_socket, 'IPV6_V6ONLY'),
4926+
"IPV6_V6ONLY option not supported")
4927+
@unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test')
4928+
def test_ipv6(self):
4929+
cli, srv = socket.socketpair(socket.AF_INET6)
4930+
cli.close()
4931+
srv.close()
4932+
4933+
def _test_ipv6(self):
4934+
pass
4935+
4936+
def test_injected_authentication_failure(self):
4937+
orig_getsockname = socket.socket.getsockname
4938+
inject_sock = None
4939+
4940+
def inject_getsocketname(self):
4941+
nonlocal inject_sock
4942+
sockname = orig_getsockname(self)
4943+
# Connect to the listening socket ahead of the
4944+
# client socket.
4945+
if inject_sock is None:
4946+
inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4947+
inject_sock.setblocking(False)
4948+
try:
4949+
inject_sock.connect(sockname[:2])
4950+
except (BlockingIOError, InterruptedError):
4951+
pass
4952+
inject_sock.setblocking(True)
4953+
return sockname
4954+
4955+
sock1 = sock2 = None
4956+
try:
4957+
socket.socket.getsockname = inject_getsocketname
4958+
with self.assertRaises(OSError):
4959+
sock1, sock2 = socket.socketpair()
4960+
finally:
4961+
socket.socket.getsockname = orig_getsockname
4962+
if inject_sock:
4963+
inject_sock.close()
4964+
if sock1: # This cleanup isn't needed on a successful test.
4965+
sock1.close()
4966+
if sock2:
4967+
sock2.close()
4968+
4969+
def _test_injected_authentication_failure(self):
4970+
# No-op. Exists for base class threading infrastructure to call.
4971+
# We could refactor this test into its own lesser class along with the
4972+
# setUp and tearDown code to construct an ideal; it is simpler to keep
4973+
# it here and live with extra overhead one this _one_ failure test.
4974+
pass
4975+
4976+
48554977
class NonBlockingTCPTests(ThreadedTCPSocketTest):
48564978

48574979
def __init__(self, methodName='runTest'):
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Authenticate the socket connection for the ``socket.socketpair()`` fallback
2+
on platforms where ``AF_UNIX`` is not available like Windows.
3+
4+
Patch by Gregory P. Smith <greg@krypto.org> and Seth Larson <seth@python.org>. Reported by Ellie
5+
<el@horse64.org>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.