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 2621a8a

Browse filesBrowse files
miss-islingtonsethmlarsongpsheadambv
authored
[3.8] gh-122133: Authenticate socket connection for socket.socketpair() fallback (GH-122134) (GH-122429)
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> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 1fb69b3 commit 2621a8a
Copy full SHA for 2621a8a

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
@@ -611,6 +611,23 @@ def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
611611
raise
612612
finally:
613613
lsock.close()
614+
615+
# Authenticating avoids using a connection from something else
616+
# able to connect to {host}:{port} instead of us.
617+
# We expect only AF_INET and AF_INET6 families.
618+
try:
619+
if (
620+
ssock.getsockname() != csock.getpeername()
621+
or csock.getsockname() != ssock.getpeername()
622+
):
623+
raise ConnectionError("Unexpected peer connection")
624+
except:
625+
# getsockname() and getpeername() can fail
626+
# if either socket isn't connected.
627+
ssock.close()
628+
csock.close()
629+
raise
630+
614631
return (ssock, csock)
615632
__all__.append("socketpair")
616633

‎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
@@ -508,19 +508,27 @@ class SocketPairTest(unittest.TestCase, ThreadableTest):
508508
def __init__(self, methodName='runTest'):
509509
unittest.TestCase.__init__(self, methodName=methodName)
510510
ThreadableTest.__init__(self)
511+
self.cli = None
512+
self.serv = None
513+
514+
def socketpair(self):
515+
# To be overridden by some child classes.
516+
return socket.socketpair()
511517

512518
def setUp(self):
513-
self.serv, self.cli = socket.socketpair()
519+
self.serv, self.cli = self.socketpair()
514520

515521
def tearDown(self):
516-
self.serv.close()
522+
if self.serv:
523+
self.serv.close()
517524
self.serv = None
518525

519526
def clientSetUp(self):
520527
pass
521528

522529
def clientTearDown(self):
523-
self.cli.close()
530+
if self.cli:
531+
self.cli.close()
524532
self.cli = None
525533
ThreadableTest.clientTearDown(self)
526534

@@ -4307,6 +4315,120 @@ def _testSend(self):
43074315
self.assertEqual(msg, MSG)
43084316

43094317

4318+
class PurePythonSocketPairTest(SocketPairTest):
4319+
4320+
# Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the
4321+
# code path we're using regardless platform is the pure python one where
4322+
# `_socket.socketpair` does not exist. (AF_INET does not work with
4323+
# _socket.socketpair on many platforms).
4324+
def socketpair(self):
4325+
# called by super().setUp().
4326+
try:
4327+
return socket.socketpair(socket.AF_INET6)
4328+
except OSError:
4329+
return socket.socketpair(socket.AF_INET)
4330+
4331+
# Local imports in this class make for easy security fix backporting.
4332+
4333+
def setUp(self):
4334+
import _socket
4335+
self._orig_sp = getattr(_socket, 'socketpair', None)
4336+
if self._orig_sp is not None:
4337+
# This forces the version using the non-OS provided socketpair
4338+
# emulation via an AF_INET socket in Lib/socket.py.
4339+
del _socket.socketpair
4340+
import importlib
4341+
global socket
4342+
socket = importlib.reload(socket)
4343+
else:
4344+
pass # This platform already uses the non-OS provided version.
4345+
super().setUp()
4346+
4347+
def tearDown(self):
4348+
super().tearDown()
4349+
import _socket
4350+
if self._orig_sp is not None:
4351+
# Restore the default socket.socketpair definition.
4352+
_socket.socketpair = self._orig_sp
4353+
import importlib
4354+
global socket
4355+
socket = importlib.reload(socket)
4356+
4357+
def test_recv(self):
4358+
msg = self.serv.recv(1024)
4359+
self.assertEqual(msg, MSG)
4360+
4361+
def _test_recv(self):
4362+
self.cli.send(MSG)
4363+
4364+
def test_send(self):
4365+
self.serv.send(MSG)
4366+
4367+
def _test_send(self):
4368+
msg = self.cli.recv(1024)
4369+
self.assertEqual(msg, MSG)
4370+
4371+
def test_ipv4(self):
4372+
cli, srv = socket.socketpair(socket.AF_INET)
4373+
cli.close()
4374+
srv.close()
4375+
4376+
def _test_ipv4(self):
4377+
pass
4378+
4379+
@unittest.skipIf(not hasattr(_socket, 'IPPROTO_IPV6') or
4380+
not hasattr(_socket, 'IPV6_V6ONLY'),
4381+
"IPV6_V6ONLY option not supported")
4382+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test')
4383+
def test_ipv6(self):
4384+
cli, srv = socket.socketpair(socket.AF_INET6)
4385+
cli.close()
4386+
srv.close()
4387+
4388+
def _test_ipv6(self):
4389+
pass
4390+
4391+
def test_injected_authentication_failure(self):
4392+
orig_getsockname = socket.socket.getsockname
4393+
inject_sock = None
4394+
4395+
def inject_getsocketname(self):
4396+
nonlocal inject_sock
4397+
sockname = orig_getsockname(self)
4398+
# Connect to the listening socket ahead of the
4399+
# client socket.
4400+
if inject_sock is None:
4401+
inject_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4402+
inject_sock.setblocking(False)
4403+
try:
4404+
inject_sock.connect(sockname[:2])
4405+
except (BlockingIOError, InterruptedError):
4406+
pass
4407+
inject_sock.setblocking(True)
4408+
return sockname
4409+
4410+
sock1 = sock2 = None
4411+
try:
4412+
socket.socket.getsockname = inject_getsocketname
4413+
with self.assertRaises(OSError):
4414+
sock1, sock2 = socket.socketpair()
4415+
finally:
4416+
socket.socket.getsockname = orig_getsockname
4417+
if inject_sock:
4418+
inject_sock.close()
4419+
if sock1: # This cleanup isn't needed on a successful test.
4420+
sock1.close()
4421+
if sock2:
4422+
sock2.close()
4423+
4424+
def _test_injected_authentication_failure(self):
4425+
# No-op. Exists for base class threading infrastructure to call.
4426+
# We could refactor this test into its own lesser class along with the
4427+
# setUp and tearDown code to construct an ideal; it is simpler to keep
4428+
# it here and live with extra overhead one this _one_ failure test.
4429+
pass
4430+
4431+
43104432
class NonBlockingTCPTests(ThreadedTCPSocketTest):
43114433

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