Skip to content

Commit e102378

Browse files
authored
[3.13] gh-142516: fix reference leaks in ssl.SSLContext objects (GH-143685) (GH-145075) (#148371)
Cherry picked from commits 3a2a686 and 1decc7e with minor amendments.
1 parent 29fcf3c commit e102378

3 files changed

Lines changed: 70 additions & 10 deletions

File tree

Lib/test/test_ssl.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@
5252
IS_OPENSSL_3_0_0 = ssl.OPENSSL_VERSION_INFO >= (3, 0, 0)
5353
PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS')
5454

55+
HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename')
56+
requires_keylog = unittest.skipUnless(
57+
HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback')
58+
CAN_SET_KEYLOG = HAS_KEYLOG and os.name != "nt"
59+
requires_keylog_setter = unittest.skipUnless(
60+
CAN_SET_KEYLOG,
61+
"cannot set 'keylog_filename' on Windows"
62+
)
63+
64+
5565
PROTOCOL_TO_TLS_VERSION = {}
5666
for proto, ver in (
5767
("PROTOCOL_SSLv3", "SSLv3"),
@@ -295,24 +305,35 @@ def make_test_context(
295305
cert_reqs=ssl.CERT_NONE,
296306
ca_certs=None, certfile=None, keyfile=None,
297307
ciphers=None,
308+
min_version=None, max_version=None,
298309
):
299310
if server_side:
300311
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
301312
else:
302313
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
314+
303315
if check_hostname is None:
304316
if cert_reqs == ssl.CERT_NONE:
305317
context.check_hostname = False
306318
else:
307319
context.check_hostname = check_hostname
320+
308321
if cert_reqs is not None:
309322
context.verify_mode = cert_reqs
323+
310324
if ca_certs is not None:
311325
context.load_verify_locations(ca_certs)
312326
if certfile is not None or keyfile is not None:
313327
context.load_cert_chain(certfile, keyfile)
328+
314329
if ciphers is not None:
315330
context.set_ciphers(ciphers)
331+
332+
if min_version is not None:
333+
context.minimum_version = min_version
334+
if max_version is not None:
335+
context.maximum_version = max_version
336+
316337
return context
317338

318339

@@ -324,6 +345,7 @@ def test_wrap_socket(
324345
cert_reqs=ssl.CERT_NONE,
325346
ca_certs=None, certfile=None, keyfile=None,
326347
ciphers=None,
348+
min_version=None, max_version=None,
327349
**kwargs,
328350
):
329351
context = make_test_context(
@@ -332,6 +354,7 @@ def test_wrap_socket(
332354
cert_reqs=cert_reqs,
333355
ca_certs=ca_certs, certfile=certfile, keyfile=keyfile,
334356
ciphers=ciphers,
357+
min_version=min_version, max_version=max_version,
335358
)
336359
if not server_side:
337360
kwargs.setdefault("server_hostname", SIGNED_CERTFILE_HOSTNAME)
@@ -1780,6 +1803,39 @@ def test_num_tickest(self):
17801803
with self.assertRaises(ValueError):
17811804
ctx.num_tickets = 1
17821805

1806+
@support.cpython_only
1807+
def test_refcycle_msg_callback(self):
1808+
# See https://github.com/python/cpython/issues/142516.
1809+
ctx = make_test_context()
1810+
def msg_callback(*args, _=ctx, **kwargs): ...
1811+
ctx._msg_callback = msg_callback
1812+
1813+
@support.cpython_only
1814+
@requires_keylog_setter
1815+
def test_refcycle_keylog_filename(self):
1816+
# See https://github.com/python/cpython/issues/142516.
1817+
self.addCleanup(os_helper.unlink, os_helper.TESTFN)
1818+
ctx = make_test_context()
1819+
class KeylogFilename(str): ...
1820+
ctx.keylog_filename = KeylogFilename(os_helper.TESTFN)
1821+
ctx.keylog_filename._ = ctx
1822+
1823+
@support.cpython_only
1824+
@unittest.skipUnless(ssl.HAS_PSK, 'requires TLS-PSK')
1825+
def test_refcycle_psk_client_callback(self):
1826+
# See https://github.com/python/cpython/issues/142516.
1827+
ctx = make_test_context()
1828+
def psk_client_callback(*args, _=ctx, **kwargs): ...
1829+
ctx.set_psk_client_callback(psk_client_callback)
1830+
1831+
@support.cpython_only
1832+
@unittest.skipUnless(ssl.HAS_PSK, 'requires TLS-PSK')
1833+
def test_refcycle_psk_server_callback(self):
1834+
# See https://github.com/python/cpython/issues/142516.
1835+
ctx = make_test_context(server_side=True)
1836+
def psk_server_callback(*args, _=ctx, **kwargs): ...
1837+
ctx.set_psk_server_callback(psk_server_callback)
1838+
17831839

17841840
class SSLErrorTests(unittest.TestCase):
17851841

@@ -5027,10 +5083,6 @@ def test_internal_chain_server(self):
50275083
self.assertEqual(res, b'\x02\n')
50285084

50295085

5030-
HAS_KEYLOG = hasattr(ssl.SSLContext, 'keylog_filename')
5031-
requires_keylog = unittest.skipUnless(
5032-
HAS_KEYLOG, 'test requires OpenSSL 1.1.1 with keylog callback')
5033-
50345086
class TestSSLDebug(unittest.TestCase):
50355087

50365088
def keylog_lines(self, fname=os_helper.TESTFN):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`ssl`: fix reference leaks in :class:`ssl.SSLContext` objects. Patch by
2+
Bénédikt Tran.

Modules/_ssl.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ typedef struct {
296296
int post_handshake_auth;
297297
#endif
298298
PyObject *msg_cb;
299-
PyObject *keylog_filename;
299+
PyObject *keylog_filename; // can be anything accepted by Py_fopen()
300300
BIO *keylog_bio;
301301
/* Cached module state, also used in SSLSocket and SSLSession code. */
302302
_sslmodulestate *state;
@@ -324,7 +324,7 @@ typedef struct {
324324
PySSLContext *ctx; /* weakref to SSL context */
325325
char shutdown_seen_zero;
326326
enum py_ssl_server_or_client socket_type;
327-
PyObject *owner; /* Python level "owner" passed to servername callback */
327+
PyObject *owner; /* weakref to Python level "owner" passed to servername callback */
328328
PyObject *server_hostname;
329329
_PySSLError err; /* last seen error from various sources */
330330
/* Some SSL callbacks don't have error reporting. Callback wrappers
@@ -2294,6 +2294,10 @@ PySSL_traverse(PySSLSocket *self, visitproc visit, void *arg)
22942294
static int
22952295
PySSL_clear(PySSLSocket *self)
22962296
{
2297+
Py_CLEAR(self->Socket);
2298+
Py_CLEAR(self->ctx);
2299+
Py_CLEAR(self->owner);
2300+
Py_CLEAR(self->server_hostname);
22972301
Py_CLEAR(self->exc);
22982302
return 0;
22992303
}
@@ -2317,10 +2321,7 @@ PySSL_dealloc(PySSLSocket *self)
23172321
SSL_set_shutdown(self->ssl, SSL_SENT_SHUTDOWN | SSL_get_shutdown(self->ssl));
23182322
SSL_free(self->ssl);
23192323
}
2320-
Py_XDECREF(self->Socket);
2321-
Py_XDECREF(self->ctx);
2322-
Py_XDECREF(self->server_hostname);
2323-
Py_XDECREF(self->owner);
2324+
(void)PySSL_clear(self);
23242325
PyObject_GC_Del(self);
23252326
Py_DECREF(tp);
23262327
}
@@ -3257,6 +3258,11 @@ context_traverse(PySSLContext *self, visitproc visit, void *arg)
32573258
{
32583259
Py_VISIT(self->set_sni_cb);
32593260
Py_VISIT(self->msg_cb);
3261+
Py_VISIT(self->keylog_filename);
3262+
#ifndef OPENSSL_NO_PSK
3263+
Py_VISIT(self->psk_client_callback);
3264+
Py_VISIT(self->psk_server_callback);
3265+
#endif
32603266
Py_VISIT(Py_TYPE(self));
32613267
return 0;
32623268
}

0 commit comments

Comments
 (0)