Skip to content

Commit

Permalink
gh-124984: Enhance ssl thread safety (#124993)
Browse files Browse the repository at this point in the history
Make SSL objects thread safe in Free Theaded build by
using critical sections.

Co-authored-by: Bénédikt Tran <[email protected]>
  • Loading branch information
ZeroIntensity and picnixz authored Oct 19, 2024
1 parent 2a378db commit 4c53b25
Show file tree
Hide file tree
Showing 4 changed files with 1,767 additions and 315 deletions.
51 changes: 51 additions & 0 deletions Lib/test/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import unittest
import unittest.mock
from ast import literal_eval
from threading import Thread
from test import support
from test.support import import_helper
from test.support import os_helper
Expand Down Expand Up @@ -277,11 +278,19 @@ def test_wrap_socket(sock, *,
return context.wrap_socket(sock, **kwargs)


USE_SAME_TEST_CONTEXT = False
_TEST_CONTEXT = None

def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True):
"""Create context
client_context, server_context, hostname = testing_context()
"""
global _TEST_CONTEXT
if USE_SAME_TEST_CONTEXT:
if _TEST_CONTEXT is not None:
return _TEST_CONTEXT

if server_cert == SIGNED_CERTFILE:
hostname = SIGNED_CERTFILE_HOSTNAME
elif server_cert == SIGNED_CERTFILE2:
Expand All @@ -299,6 +308,10 @@ def testing_context(server_cert=SIGNED_CERTFILE, *, server_chain=True):
if server_chain:
server_context.load_verify_locations(SIGNING_CA)

if USE_SAME_TEST_CONTEXT:
if _TEST_CONTEXT is not None:
_TEST_CONTEXT = client_context, server_context, hostname

return client_context, server_context, hostname


Expand Down Expand Up @@ -2801,6 +2814,44 @@ def test_echo(self):
'Cannot create a client socket with a PROTOCOL_TLS_SERVER context',
str(e.exception))

@unittest.skipUnless(support.Py_GIL_DISABLED, "test is only useful if the GIL is disabled")
def test_ssl_in_multiple_threads(self):
# See GH-124984: OpenSSL is not thread safe.
threads = []

global USE_SAME_TEST_CONTEXT
USE_SAME_TEST_CONTEXT = True
try:
for func in (
self.test_echo,
self.test_alpn_protocols,
self.test_getpeercert,
self.test_crl_check,
self.test_check_hostname_idn,
self.test_wrong_cert_tls12,
self.test_wrong_cert_tls13,
):
# Be careful with the number of threads here.
# Too many can result in failing tests.
for num in range(5):
with self.subTest(func=func, num=num):
threads.append(Thread(target=func))

with threading_helper.catch_threading_exception() as cm:
for thread in threads:
with self.subTest(thread=thread):
thread.start()

for thread in threads:
with self.subTest(thread=thread):
thread.join()
if cm.exc_value is not None:
# Some threads can skip their test
if not isinstance(cm.exc_value, unittest.SkipTest):
raise cm.exc_value
finally:
USE_SAME_TEST_CONTEXT = False

def test_getpeercert(self):
if support.verbose:
sys.stdout.write("\n")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed thread safety in :mod:`ssl` in the free-threaded build. OpenSSL operations are now protected by a per-object lock.
Loading

0 comments on commit 4c53b25

Please sign in to comment.