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 9f83e2b

Browse filesBrowse files
sethmlarsongpshead
authored andcommitted
pythongh-122133: Authenticate socket connection for socket.socketpair() fallback (pythonGH-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). (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 9e9c71d commit 9f83e2b
Copy full SHA for 9f83e2b

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

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

559565
def setUp(self):
560-
self.serv, self.cli = socket.socketpair()
566+
self.serv, self.cli = self.socketpair()
561567

562568
def tearDown(self):
563-
self.serv.close()
569+
if self.serv:
570+
self.serv.close()
564571
self.serv = None
565572

566573
def clientSetUp(self):
567574
pass
568575

569576
def clientTearDown(self):
570-
self.cli.close()
577+
if self.cli:
578+
self.cli.close()
571579
self.cli = None
572580
ThreadableTest.clientTearDown(self)
573581

@@ -4613,6 +4621,120 @@ def _testSend(self):
46134621
self.assertEqual(msg, MSG)
46144622

46154623

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

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