Skip to content

Commit 5aa7ad2

Browse files
committed
[3.13] gh-142516: fix reference leaks in ssl.SSLContext objects (GH-143685) (GH-145075)
* [3.14] gh-142516: fix reference leaks in `ssl.SSLContext` objects (GH-143685) (cherry picked from commit 3a2a686) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> * fix backport (cherry picked from commit 1decc7e) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent a14e4e3 commit 5aa7ad2

3 files changed

Lines changed: 74 additions & 11 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: 16 additions & 7 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
@@ -334,6 +334,8 @@ typedef struct {
334334
PyObject *exc;
335335
} PySSLSocket;
336336

337+
#define PySSLSocket_CAST(op) ((PySSLSocket *)(op))
338+
337339
typedef struct {
338340
PyObject_HEAD
339341
BIO *bio;
@@ -2292,8 +2294,13 @@ PySSL_traverse(PySSLSocket *self, visitproc visit, void *arg)
22922294
}
22932295

22942296
static int
2295-
PySSL_clear(PySSLSocket *self)
2297+
PySSL_clear(PySSLSocket *op)
22962298
{
2299+
PySSLSocket *self = PySSLSocket_CAST(op);
2300+
Py_CLEAR(self->Socket);
2301+
Py_CLEAR(self->ctx);
2302+
Py_CLEAR(self->owner);
2303+
Py_CLEAR(self->server_hostname);
22972304
Py_CLEAR(self->exc);
22982305
return 0;
22992306
}
@@ -2317,10 +2324,7 @@ PySSL_dealloc(PySSLSocket *self)
23172324
SSL_set_shutdown(self->ssl, SSL_SENT_SHUTDOWN | SSL_get_shutdown(self->ssl));
23182325
SSL_free(self->ssl);
23192326
}
2320-
Py_XDECREF(self->Socket);
2321-
Py_XDECREF(self->ctx);
2322-
Py_XDECREF(self->server_hostname);
2323-
Py_XDECREF(self->owner);
2327+
(void)PySSL_clear(self);
23242328
PyObject_GC_Del(self);
23252329
Py_DECREF(tp);
23262330
}
@@ -3257,6 +3261,11 @@ context_traverse(PySSLContext *self, visitproc visit, void *arg)
32573261
{
32583262
Py_VISIT(self->set_sni_cb);
32593263
Py_VISIT(self->msg_cb);
3264+
Py_VISIT(self->keylog_filename);
3265+
#ifndef OPENSSL_NO_PSK
3266+
Py_VISIT(self->psk_client_callback);
3267+
Py_VISIT(self->psk_server_callback);
3268+
#endif
32603269
Py_VISIT(Py_TYPE(self));
32613270
return 0;
32623271
}

0 commit comments

Comments
 (0)