aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2016-11-13 15:55:22 -0500
committerPaul Kehrer <paul.l.kehrer@gmail.com>2016-11-13 12:55:22 -0800
commitd862933de5c344fcdf99ab2f43f3bf8da65f3e41 (patch)
tree16d73c47a45f939336fe5e41bf45293097a5a222 /src
parent562b9a905596f3e58b27be584a9532aa3a4dc833 (diff)
downloadcryptography-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.py73
-rw-r--r--src/cryptography/hazmat/bindings/openssl/binding.py31
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):