mirror of
https://github.com/python/cpython.git
synced 2026-10-28 18:35:21 +08:00
gh-159739: Release SSL resources on error paths (GH-159745)
Free decoded subject alternative names when certificate conversion
fails, and close the key log file if the BIO allocation fails.
Backport to 3.15 and 3.14.
(cherry picked from commit b2b0a33709)
Co-authored-by: Jelle Zijlstra <jelle@openai.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
241 lines
7.0 KiB
C
241 lines
7.0 KiB
C
/* Debug helpers */
|
|
|
|
#ifndef SSL3_MT_CHANGE_CIPHER_SPEC
|
|
/* Dummy message type for handling CCS like a normal handshake message
|
|
* not defined in OpenSSL 1.0.2
|
|
*/
|
|
#define SSL3_MT_CHANGE_CIPHER_SPEC 0x0101
|
|
#endif
|
|
|
|
static void
|
|
_PySSL_msg_callback(int write_p, int version, int content_type,
|
|
const void *buf, size_t len, SSL *ssl, void *arg)
|
|
{
|
|
const char *cbuf = (const char *)buf;
|
|
PyGILState_STATE threadstate;
|
|
PyObject *res = NULL;
|
|
PySSLSocket *ssl_obj = NULL; /* ssl._SSLSocket, borrowed ref */
|
|
int msg_type;
|
|
|
|
threadstate = PyGILState_Ensure();
|
|
|
|
ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl);
|
|
assert(Py_IS_TYPE(ssl_obj, get_state_sock(ssl_obj)->PySSLSocket_Type));
|
|
if (ssl_obj->ctx->msg_cb == NULL) {
|
|
PyGILState_Release(threadstate);
|
|
return;
|
|
}
|
|
|
|
PyObject *ssl_socket; /* ssl.SSLSocket or ssl.SSLObject */
|
|
if (ssl_obj->owner)
|
|
PyWeakref_GetRef(ssl_obj->owner, &ssl_socket);
|
|
else if (ssl_obj->Socket)
|
|
PyWeakref_GetRef(ssl_obj->Socket, &ssl_socket);
|
|
else
|
|
ssl_socket = (PyObject *)Py_NewRef(ssl_obj);
|
|
assert(ssl_socket != NULL); // PyWeakref_GetRef() can return NULL
|
|
|
|
/* assume that OpenSSL verifies all payload and buf len is of sufficient
|
|
length */
|
|
switch(content_type) {
|
|
case SSL3_RT_CHANGE_CIPHER_SPEC:
|
|
msg_type = SSL3_MT_CHANGE_CIPHER_SPEC;
|
|
break;
|
|
case SSL3_RT_ALERT:
|
|
/* byte 0: level */
|
|
/* byte 1: alert type */
|
|
msg_type = (int)cbuf[1];
|
|
break;
|
|
case SSL3_RT_HANDSHAKE:
|
|
msg_type = (int)cbuf[0];
|
|
break;
|
|
#ifdef SSL3_RT_HEADER
|
|
case SSL3_RT_HEADER:
|
|
/* frame header encodes version in bytes 1..2 */
|
|
version = cbuf[1] << 8 | cbuf[2];
|
|
msg_type = (int)cbuf[0];
|
|
break;
|
|
#endif
|
|
#ifdef SSL3_RT_INNER_CONTENT_TYPE
|
|
case SSL3_RT_INNER_CONTENT_TYPE:
|
|
msg_type = (int)cbuf[0];
|
|
break;
|
|
#endif
|
|
default:
|
|
/* never SSL3_RT_APPLICATION_DATA */
|
|
msg_type = -1;
|
|
break;
|
|
}
|
|
|
|
res = PyObject_CallFunction(
|
|
ssl_obj->ctx->msg_cb, "Osiiiy#",
|
|
ssl_socket, write_p ? "write" : "read",
|
|
version, content_type, msg_type,
|
|
buf, len
|
|
);
|
|
if (res == NULL) {
|
|
ssl_obj->exc = PyErr_GetRaisedException();
|
|
} else {
|
|
Py_DECREF(res);
|
|
}
|
|
Py_XDECREF(ssl_socket);
|
|
|
|
PyGILState_Release(threadstate);
|
|
}
|
|
|
|
|
|
static PyObject *
|
|
_PySSLContext_get_msg_callback(PyObject *op, void *Py_UNUSED(closure))
|
|
{
|
|
PySSLContext *self = PySSLContext_CAST(op);
|
|
if (self->msg_cb != NULL) {
|
|
return Py_NewRef(self->msg_cb);
|
|
} else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
static int
|
|
_PySSLContext_set_msg_callback(PyObject *op, PyObject *arg,
|
|
void *Py_UNUSED(closure))
|
|
{
|
|
PySSLContext *self = PySSLContext_CAST(op);
|
|
if (arg == NULL) {
|
|
PyErr_Format(PyExc_AttributeError,
|
|
"attribute '_msg_callback' of '%.100s' objects "
|
|
"cannot be deleted", Py_TYPE(op)->tp_name);
|
|
return -1;
|
|
}
|
|
if (arg != Py_None && !PyCallable_Check(arg)) {
|
|
PyErr_SetString(PyExc_TypeError,
|
|
"not a callable object");
|
|
return -1;
|
|
}
|
|
/* Releasing the old callback can run arbitrary code. */
|
|
PyObject *old_cb = self->msg_cb;
|
|
if (arg == Py_None) {
|
|
self->msg_cb = NULL;
|
|
SSL_CTX_set_msg_callback(self->ctx, NULL);
|
|
}
|
|
else {
|
|
self->msg_cb = Py_NewRef(arg);
|
|
SSL_CTX_set_msg_callback(self->ctx, _PySSL_msg_callback);
|
|
}
|
|
Py_XDECREF(old_cb);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_PySSL_keylog_callback(const SSL *ssl, const char *line)
|
|
{
|
|
PyGILState_STATE threadstate;
|
|
PySSLSocket *ssl_obj = NULL; /* ssl._SSLSocket, borrowed ref */
|
|
int res, e;
|
|
|
|
threadstate = PyGILState_Ensure();
|
|
|
|
ssl_obj = (PySSLSocket *)SSL_get_app_data(ssl);
|
|
assert(Py_IS_TYPE(ssl_obj, get_state_sock(ssl_obj)->PySSLSocket_Type));
|
|
PyThread_type_lock lock = get_state_sock(ssl_obj)->keylog_lock;
|
|
assert(lock != NULL);
|
|
if (ssl_obj->ctx->keylog_bio == NULL) {
|
|
return;
|
|
}
|
|
/*
|
|
* The lock is neither released on exit nor on fork(). The lock is
|
|
* also shared between all SSLContexts although contexts may write to
|
|
* their own files. IMHO that's good enough for a non-performance
|
|
* critical debug helper.
|
|
*/
|
|
|
|
Py_BEGIN_ALLOW_THREADS
|
|
PyThread_acquire_lock(lock, 1);
|
|
res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line);
|
|
e = errno;
|
|
(void)BIO_flush(ssl_obj->ctx->keylog_bio);
|
|
PyThread_release_lock(lock);
|
|
Py_END_ALLOW_THREADS
|
|
_PySSL_FIX_ERRNO;
|
|
|
|
if (res == -1) {
|
|
errno = e;
|
|
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError,
|
|
ssl_obj->ctx->keylog_filename);
|
|
ssl_obj->exc = PyErr_GetRaisedException();
|
|
}
|
|
PyGILState_Release(threadstate);
|
|
}
|
|
|
|
static PyObject *
|
|
_PySSLContext_get_keylog_filename(PyObject *op, void *Py_UNUSED(closure))
|
|
{
|
|
PySSLContext *self = PySSLContext_CAST(op);
|
|
if (self->keylog_filename != NULL) {
|
|
return Py_NewRef(self->keylog_filename);
|
|
} else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
static int
|
|
_PySSLContext_set_keylog_filename(PyObject *op, PyObject *arg,
|
|
void *Py_UNUSED(closure))
|
|
{
|
|
PySSLContext *self = PySSLContext_CAST(op);
|
|
FILE *fp;
|
|
|
|
if (arg == NULL) {
|
|
PyErr_Format(PyExc_AttributeError,
|
|
"attribute 'keylog_filename' of '%.100s' objects "
|
|
"cannot be deleted", Py_TYPE(op)->tp_name);
|
|
return -1;
|
|
}
|
|
#if defined(MS_WINDOWS) && defined(_DEBUG)
|
|
PyErr_SetString(PyExc_NotImplementedError,
|
|
"set_keylog_filename: unavailable on Windows debug build");
|
|
return -1;
|
|
#endif
|
|
|
|
/* Reset variables and callback first */
|
|
SSL_CTX_set_keylog_callback(self->ctx, NULL);
|
|
Py_CLEAR(self->keylog_filename);
|
|
if (self->keylog_bio != NULL) {
|
|
BIO *bio = self->keylog_bio;
|
|
self->keylog_bio = NULL;
|
|
Py_BEGIN_ALLOW_THREADS
|
|
BIO_free_all(bio);
|
|
Py_END_ALLOW_THREADS
|
|
_PySSL_FIX_ERRNO;
|
|
}
|
|
|
|
if (arg == Py_None) {
|
|
/* None disables the callback */
|
|
return 0;
|
|
}
|
|
|
|
/* Py_fopen() also checks that arg is of proper type. */
|
|
fp = Py_fopen(arg, "a" PY_STDIOTEXTMODE);
|
|
if (fp == NULL)
|
|
return -1;
|
|
|
|
self->keylog_bio = BIO_new_fp(fp, BIO_CLOSE | BIO_FP_TEXT);
|
|
if (self->keylog_bio == NULL) {
|
|
fclose(fp);
|
|
PyErr_SetString(get_state_ctx(self)->PySSLErrorObject,
|
|
"Can't malloc memory for keylog file");
|
|
return -1;
|
|
}
|
|
self->keylog_filename = Py_NewRef(arg);
|
|
|
|
/* Write a header for seekable, empty files (this excludes pipes). */
|
|
PySSL_BEGIN_ALLOW_THREADS(self)
|
|
if (BIO_tell(self->keylog_bio) == 0) {
|
|
BIO_puts(self->keylog_bio,
|
|
"# TLS secrets log file, generated by OpenSSL / Python\n");
|
|
(void)BIO_flush(self->keylog_bio);
|
|
}
|
|
PySSL_END_ALLOW_THREADS(self)
|
|
SSL_CTX_set_keylog_callback(self->ctx, _PySSL_keylog_callback);
|
|
return 0;
|
|
}
|