Skip to content

Commit 7e7223e

Browse files
jeffvanvoorstblurb-it[bot]ZeroIntensitygpsheadpitrou
authored
pythongh-116810: fix memory leak in ssl module (pythonGH-123249)
Resolve a memory leak introduced in CPython 3.10's :mod:`ssl` when the :attr:`ssl.SSLSocket.session` property was accessed. Speeds up read and write access to said property by no longer unnecessarily cloning session objects via serialization. Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Peter Bierma <[email protected]> Co-authored-by: Gregory P. Smith <[email protected]> Co-authored-by: Antoine Pitrou <[email protected]>
1 parent 1c0bd8b commit 7e7223e

File tree

2 files changed

+17
-63
lines changed

2 files changed

+17
-63
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Resolve a memory leak introduced in CPython 3.10's :mod:`ssl` when the
2+
:attr:`ssl.SSLSocket.session` property was accessed. Speeds up read and
3+
write access to said property by no longer unnecessarily cloning session
4+
objects via serialization.

Modules/_ssl.c

Lines changed: 13 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,17 @@ PySSL_dealloc(PySSLSocket *self)
22512251
PyTypeObject *tp = Py_TYPE(self);
22522252
PyObject_GC_UnTrack(self);
22532253
if (self->ssl) {
2254+
// If we free the SSL socket object without having called SSL_shutdown,
2255+
// OpenSSL will invalidate the linked SSL session object. While this
2256+
// behavior is strictly RFC-compliant, it makes session reuse less
2257+
// likely and it would also break compatibility with older stdlib
2258+
// versions (which used an ugly workaround of duplicating the
2259+
// SSL_SESSION object).
2260+
// Therefore, we ensure the socket is marked as shutdown in any case.
2261+
//
2262+
// See elaborate explanation at
2263+
// https://github.com/python/cpython/pull/123249#discussion_r1766164530
2264+
SSL_set_shutdown(self->ssl, SSL_SENT_SHUTDOWN | SSL_get_shutdown(self->ssl));
22542265
SSL_free(self->ssl);
22552266
}
22562267
Py_XDECREF(self->Socket);
@@ -2795,64 +2806,13 @@ _ssl__SSLSocket_verify_client_post_handshake_impl(PySSLSocket *self)
27952806
#endif
27962807
}
27972808

2798-
static SSL_SESSION*
2799-
_ssl_session_dup(SSL_SESSION *session) {
2800-
SSL_SESSION *newsession = NULL;
2801-
int slen;
2802-
unsigned char *senc = NULL, *p;
2803-
const unsigned char *const_p;
2804-
2805-
if (session == NULL) {
2806-
PyErr_SetString(PyExc_ValueError, "Invalid session");
2807-
goto error;
2808-
}
2809-
2810-
/* get length */
2811-
slen = i2d_SSL_SESSION(session, NULL);
2812-
if (slen == 0 || slen > 0xFF00) {
2813-
PyErr_SetString(PyExc_ValueError, "i2d() failed");
2814-
goto error;
2815-
}
2816-
if ((senc = PyMem_Malloc(slen)) == NULL) {
2817-
PyErr_NoMemory();
2818-
goto error;
2819-
}
2820-
p = senc;
2821-
if (!i2d_SSL_SESSION(session, &p)) {
2822-
PyErr_SetString(PyExc_ValueError, "i2d() failed");
2823-
goto error;
2824-
}
2825-
const_p = senc;
2826-
newsession = d2i_SSL_SESSION(NULL, &const_p, slen);
2827-
if (newsession == NULL) {
2828-
PyErr_SetString(PyExc_ValueError, "d2i() failed");
2829-
goto error;
2830-
}
2831-
PyMem_Free(senc);
2832-
return newsession;
2833-
error:
2834-
if (senc != NULL) {
2835-
PyMem_Free(senc);
2836-
}
2837-
return NULL;
2838-
}
2839-
28402809
static PyObject *
28412810
PySSL_get_session(PySSLSocket *self, void *closure) {
28422811
/* get_session can return sessions from a server-side connection,
28432812
* it does not check for handshake done or client socket. */
28442813
PySSLSession *pysess;
28452814
SSL_SESSION *session;
28462815

2847-
/* duplicate session as workaround for session bug in OpenSSL 1.1.0,
2848-
* https://github.com/openssl/openssl/issues/1550 */
2849-
session = SSL_get0_session(self->ssl); /* borrowed reference */
2850-
if (session == NULL) {
2851-
Py_RETURN_NONE;
2852-
}
2853-
if ((session = _ssl_session_dup(session)) == NULL) {
2854-
return NULL;
2855-
}
28562816
session = SSL_get1_session(self->ssl);
28572817
if (session == NULL) {
28582818
Py_RETURN_NONE;
@@ -2871,11 +2831,8 @@ PySSL_get_session(PySSLSocket *self, void *closure) {
28712831
}
28722832

28732833
static int PySSL_set_session(PySSLSocket *self, PyObject *value,
2874-
void *closure)
2875-
{
2834+
void *closure) {
28762835
PySSLSession *pysess;
2877-
SSL_SESSION *session;
2878-
int result;
28792836

28802837
if (!Py_IS_TYPE(value, get_state_sock(self)->PySSLSession_Type)) {
28812838
PyErr_SetString(PyExc_TypeError, "Value is not a SSLSession.");
@@ -2898,14 +2855,7 @@ static int PySSL_set_session(PySSLSocket *self, PyObject *value,
28982855
"Cannot set session after handshake.");
28992856
return -1;
29002857
}
2901-
/* duplicate session */
2902-
if ((session = _ssl_session_dup(pysess->session)) == NULL) {
2903-
return -1;
2904-
}
2905-
result = SSL_set_session(self->ssl, session);
2906-
/* free duplicate, SSL_set_session() bumps ref count */
2907-
SSL_SESSION_free(session);
2908-
if (result == 0) {
2858+
if (SSL_set_session(self->ssl, pysess->session) == 0) {
29092859
_setSSLError(get_state_sock(self), NULL, 0, __FILE__, __LINE__);
29102860
return -1;
29112861
}

0 commit comments

Comments
 (0)