From d862933de5c344fcdf99ab2f43f3bf8da65f3e41 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 13 Nov 2016 15:55:22 -0500 Subject: C locking callback (#3226) * Remove Python OpenSSL locking callback and replace it with one in C The Python OpenSSL locking callback is unsafe; if GC is triggered during the callback's invocation, it can result in the callback being invoked reentrantly, which can lead to deadlocks. This patch replaces it with one in C that gets built at compile time via cffi along with the rest of the OpenSSL binding. * fixes for some issues * unused * revert these changes * these two for good measure * missing param * sigh, syntax * delete tests that assumed an ability to mess with locks * style fixes * licensing stuff * utf8 * Unicode. Huh. What it isn't good for, absolutely nothing. --- src/_cffi_src/openssl/callbacks.py | 73 +++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) (limited to 'src/_cffi_src') diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py index 5ae89b18..4a6b4d37 100644 --- a/src/_cffi_src/openssl/callbacks.py +++ b/src/_cffi_src/openssl/callbacks.py @@ -12,6 +12,9 @@ INCLUDES = """ #include #include #include +#include + +#include """ TYPES = """ @@ -37,6 +40,7 @@ extern "Python" int Cryptography_rand_status(void); """ FUNCTIONS = """ +int _setup_ssl_threads(void); """ MACROS = """ @@ -50,4 +54,71 @@ if cffi.__version_info__ < (1, 4, 0) or sys.version_info >= (3, 5): # backwards compatibility for old cffi version on PyPy # and Python >=3.5 (https://github.com/pyca/cryptography/issues/2970) TYPES = "static const long Cryptography_STATIC_CALLBACKS;" - CUSTOMIZATIONS = "static const long Cryptography_STATIC_CALLBACKS = 0;" + CUSTOMIZATIONS = """static const long Cryptography_STATIC_CALLBACKS = 0; +""" + +CUSTOMIZATIONS += """ +/* This code is derived from the locking code found in the Python _ssl module's + locking callback for OpenSSL. + + Copyright 2001-2016 Python Software Foundation; All Rights Reserved. +*/ + +static unsigned int _ssl_locks_count = 0; +static PyThread_type_lock *_ssl_locks = NULL; + +static void _ssl_thread_locking_function(int mode, int n, const char *file, + int line) { + /* this function is needed to perform locking on shared data + structures. (Note that OpenSSL uses a number of global data + structures that will be implicitly shared whenever multiple + threads use OpenSSL.) Multi-threaded applications will + crash at random if it is not set. + + locking_function() must be able to handle up to + CRYPTO_num_locks() different mutex locks. It sets the n-th + lock if mode & CRYPTO_LOCK, and releases it otherwise. + + file and line are the file number of the function setting the + lock. They can be useful for debugging. + */ + + if ((_ssl_locks == NULL) || + (n < 0) || ((unsigned)n >= _ssl_locks_count)) { + return; + } + + if (mode & CRYPTO_LOCK) { + PyThread_acquire_lock(_ssl_locks[n], 1); + } else { + PyThread_release_lock(_ssl_locks[n]); + } +} + +int _setup_ssl_threads(void) { + unsigned int i; + + if (_ssl_locks == NULL) { + _ssl_locks_count = CRYPTO_num_locks(); + _ssl_locks = PyMem_New(PyThread_type_lock, _ssl_locks_count); + if (_ssl_locks == NULL) { + PyErr_NoMemory(); + return 0; + } + memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count); + for (i = 0; i < _ssl_locks_count; i++) { + _ssl_locks[i] = PyThread_allocate_lock(); + if (_ssl_locks[i] == NULL) { + unsigned int j; + for (j = 0; j < i; j++) { + PyThread_free_lock(_ssl_locks[j]); + } + PyMem_Free(_ssl_locks); + return 0; + } + } + CRYPTO_set_locking_callback(_ssl_thread_locking_function); + } + return 1; +} +""" -- cgit v1.2.3