diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2016-11-13 15:55:22 -0500 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2016-11-13 12:55:22 -0800 |
commit | d862933de5c344fcdf99ab2f43f3bf8da65f3e41 (patch) | |
tree | 16d73c47a45f939336fe5e41bf45293097a5a222 /src | |
parent | 562b9a905596f3e58b27be584a9532aa3a4dc833 (diff) | |
download | cryptography-d862933de5c344fcdf99ab2f43f3bf8da65f3e41.tar.gz cryptography-d862933de5c344fcdf99ab2f43f3bf8da65f3e41.tar.bz2 cryptography-d862933de5c344fcdf99ab2f43f3bf8da65f3e41.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/_cffi_src/openssl/callbacks.py | 73 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/binding.py | 31 |
2 files changed, 74 insertions, 30 deletions
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 <openssl/ssl.h> #include <openssl/x509.h> #include <openssl/x509_vfy.h> +#include <openssl/crypto.h> + +#include <pythread.h> """ 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; +} +""" diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index e788502d..25849bf3 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -118,8 +118,6 @@ class Binding(object): lib = None ffi = ffi _lib_loaded = False - _locks = None - _lock_cb_handle = None _init_lock = threading.Lock() _lock_init_lock = threading.Lock() @@ -178,14 +176,6 @@ class Binding(object): def init_static_locks(cls): with cls._lock_init_lock: cls._ensure_ffi_initialized() - - if not cls._lock_cb_handle: - wrapper = ffi_callback( - "void(int, int, const char *, int)", - name="Cryptography_locking_cb", - ) - cls._lock_cb_handle = wrapper(cls._lock_cb) - # Use Python's implementation if available, importing _ssl triggers # the setup for this. __import__("_ssl") @@ -195,25 +185,8 @@ class Binding(object): # If nothing else has setup a locking callback already, we set up # our own - num_locks = cls.lib.CRYPTO_num_locks() - cls._locks = [threading.Lock() for n in range(num_locks)] - - cls.lib.CRYPTO_set_locking_callback(cls._lock_cb_handle) - - @classmethod - def _lock_cb(cls, mode, n, file, line): - lock = cls._locks[n] - - if mode & cls.lib.CRYPTO_LOCK: - lock.acquire() - elif mode & cls.lib.CRYPTO_UNLOCK: - lock.release() - else: - raise RuntimeError( - "Unknown lock mode {0}: lock={1}, file={2}, line={3}.".format( - mode, n, file, line - ) - ) + res = lib._setup_ssl_threads() + _openssl_assert(cls.lib, res == 1) def _verify_openssl_version(version): |