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 0b65c8b

Browse filesBrowse files
miss-islingtonsethmlarsongpshead
authored
[3.10] gh-122133: Authenticate socket connection for socket.socketpair() fallback (GH-122134) (#122427)
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). (cherry picked from commit 78df104) Co-authored-by: Seth Michael Larson <seth@python.org> Co-authored-by: Gregory P. Smith <greg@krypto.org>
1 parent d86ab5d commit 0b65c8b
Copy full SHA for 0b65c8b

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
@@ -647,6 +647,23 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
647647
raise
648648
finally:
649649
lsock.close()
650+
651+
# Authenticating avoids using a connection from something else
652+
# able to connect to {host}:{port} instead of us.
653+
# We expect only AF_INET and AF_INET6 families.
654+
try:
655+
if (
656+
ssock.getsockname() != csock.getpeername()
657+
or csock.getsockname() != ssock.getpeername()
658+
):
659+
raise ConnectionError("Unexpected peer connection")
660+
except:
661+
# getsockname() and getpeername() can fail
662+
# if either socket isn't connected.
663+
ssock.close()
664+
csock.close()
665+
raise
666+
650667
return (ssock, csock)
651668
__all__.append("socketpair")
652669

‎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
@@ -557,19 +557,27 @@ class SocketPairTest(unittest.TestCase, ThreadableTest):
557557
def __init__(self, methodName='runTest'):
558558
unittest.TestCase.__init__(self, methodName=methodName)
559559
ThreadableTest.__init__(self)
560+
self.cli = None
561+
self.serv = None
562+
563+
def socketpair(self):
564+
# To be overridden by some child classes.
565+
return socket.socketpair()
560566

561567
def setUp(self):
562-
self.serv, self.cli = socket.socketpair()
568+
self.serv, self.cli = self.socketpair()
563569

564570
def tearDown(self):
565-
self.serv.close()
571+
if self.serv:
572+
self.serv.close()
566573
self.serv = None
567574

568575
def clientSetUp(self):
569576
pass
570577

571578
def clientTearDown(self):
572-
self.cli.close()
579+
if self.cli:
580+
self.cli.close()
573581
self.cli = None
574582
ThreadableTest.clientTearDown(self)
575583

@@ -4630,6 +4638,120 @@ def _testSend(self):
46304638
self.assertEqual(msg, MSG)
46314639

46324640

4641+
class PurePythonSocketPairTest(SocketPairTest):
4642+
4643+
# Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the
4644+
# code path we're using regardless platform is the pure python one where
4645+
# `_socket.socketpair` does not exist. (AF_INET does not work with
4646+
# _socket.socketpair on many platforms).
4647+
def socketpair(self):
4648+
# called by super().setUp().
4649+
try:
4650+
return socket.socketpair(socket.AF_INET6)
4651+
except OSError:
4652+
return socket.socketpair(socket.AF_INET)
4653+
4654+
# Local imports in this class make for easy security fix backporting.
4655+
4656+
def setUp(self):
4657+
import _socket
4658+
self._orig_sp = getattr(_socket, 'socketpair', None)
4659+
if self._orig_sp is not None:
4660+
# This forces the version using the non-OS provided socketpair
4661+
# emulation via an AF_INET socket in Lib/socket.py.
4662+
del _socket.socketpair
4663+
import importlib
4664+
global socket
4665+
socket = importlib.reload(socket)
4666+
else:
4667+
pass # This platform already uses the non-OS provided version.
4668+
super().setUp()
4669+
4670+
def tearDown(self):
4671+
super().tearDown()
4672+
import _socket
4673+
if self._orig_sp is not None:
4674+
# Restore the default socket.socketpair definition.
4675+
_socket.socketpair = self._orig_sp
4676+
import importlib
4677+
global socket
4678+
socket = importlib.reload(socket)
4679+
4680+
def test_recv(self):
4681+
msg = self.serv.recv(1024)
4682+
self.assertEqual(msg, MSG)
4683+
4684+
def _test_recv(self):
4685+
self.cli.send(MSG)
4686+
4687+
def test_send(self):
4688+
self.serv.send(MSG)
4689+
4690+
def _test_send(self):
4691+
msg = self.cli.recv(1024)
4692+
self.assertEqual(msg, MSG)
4693+
4694+
def test_ipv4(self):
4695+
cli, srv = socket.socketpair(socket.AF_INET)
4696+
cli.close()
4697+
srv.close()
4698+
4699+
def _test_ipv4(self):
4700+
pass
4701+
4702+
@unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or
4703+
not hasattr(_socket, 'IPV6_V6ONLY'),
4704+
"IPV6_V6ONLY option not supported")
4705+
@unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test')
4706+
def test_ipv6(self):
4707+
cli, srv = socket.socketpair(socket.AF_INET6)
4708+
cli.close()
4709+
srv.close()
4710+
4711+
def _test_ipv6(self):
4712+
pass
4713+
4714+
def test_injected_authentication_failure(self):
4715+
orig_getsockname = socket.socket.getsockname
4716+
inject_sock = None
4717+
4718+
def inject_getsocketname(self):
4719+
nonlocal inject_sock
4720+
sockname = orig_getsockname(self)
4721+
# Connect to the listening socket ahead of the
4722+
# client socket.
4723+
if inject_sock is None:
4724+
inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4725+
inject_sock.setblocking(False)
4726+
try:
4727+
inject_sock.connect(sockname[:2])
4728+
except (BlockingIOError, InterruptedError):
4729+
pass
4730+
inject_sock.setblocking(True)
4731+
return sockname
4732+
4733+
sock1 = sock2 = None
4734+
try:
4735+
socket.socket.getsockname = inject_getsocketname
4736+
with self.assertRaises(OSError):
4737+
sock1, sock2 = socket.socketpair()
4738+
finally:
4739+
socket.socket.getsockname = orig_getsockname
4740+
if inject_sock:
4741+
inject_sock.close()
4742+
if sock1: # This cleanup isn't needed on a successful test.
4743+
sock1.close()
4744+
if sock2:
4745+
sock2.close()
4746+
4747+
def _test_injected_authentication_failure(self):
4748+
# No-op. Exists for base class threading infrastructure to call.
4749+
# We could refactor this test into its own lesser class along with the
4750+
# setUp and tearDown code to construct an ideal; it is simpler to keep
4751+
# it here and live with extra overhead one this _one_ failure test.
4752+
pass
4753+
4754+
46334755
class NonBlockingTCPTests(ThreadedTCPSocketTest):
46344756

46354757
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.