Skip to content

gh-111916: Make hashlib related modules thread-safe without the GIL #111981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions Modules/_blake2/blake2b_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# define Py_BUILD_CORE_MODULE 1
#endif

#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()

Expand All @@ -42,7 +43,8 @@ typedef struct {
PyObject_HEAD
blake2b_param param;
blake2b_state state;
PyThread_type_lock lock;
bool use_mutex;
PyMutex mutex;
} BLAKE2bObject;

#include "clinic/blake2b_impl.c.h"
Expand All @@ -59,9 +61,11 @@ new_BLAKE2bObject(PyTypeObject *type)
{
BLAKE2bObject *self;
self = (BLAKE2bObject *)type->tp_alloc(type, 0);
if (self != NULL) {
self->lock = NULL;
if (self == NULL) {
return NULL;
}
INIT_MUTEX(self);

return self;
}

Expand Down Expand Up @@ -278,18 +282,17 @@ _blake2_blake2b_update(BLAKE2bObject *self, PyObject *data)

GET_BUFFER_VIEW_OR_ERROUT(data, &buf);

if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE)
self->lock = PyThread_allocate_lock();

if (self->lock != NULL) {
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(self->lock, 1);
blake2b_update(&self->state, buf.buf, buf.len);
PyThread_release_lock(self->lock);
Py_END_ALLOW_THREADS
self->use_mutex = true;
if (buf.len >= HASHLIB_GIL_MINSIZE) {
Py_BEGIN_ALLOW_THREADS
PyMutex_Lock(&self->mutex);
blake2b_update(&self->state, buf.buf, buf.len);
PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
blake2b_update(&self->state, buf.buf, buf.len);
}

PyBuffer_Release(&buf);

Py_RETURN_NONE;
Expand Down Expand Up @@ -389,10 +392,6 @@ py_blake2b_dealloc(PyObject *self)
/* Try not to leave state in memory. */
secure_zero_memory(&obj->param, sizeof(obj->param));
secure_zero_memory(&obj->state, sizeof(obj->state));
if (obj->lock) {
PyThread_free_lock(obj->lock);
obj->lock = NULL;
}

PyTypeObject *type = Py_TYPE(self);
PyObject_Free(self);
Expand Down
43 changes: 21 additions & 22 deletions Modules/_blake2/blake2s_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# define Py_BUILD_CORE_MODULE 1
#endif

#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()

Expand All @@ -42,7 +43,8 @@ typedef struct {
PyObject_HEAD
blake2s_param param;
blake2s_state state;
PyThread_type_lock lock;
bool use_mutex;
PyMutex mutex;
} BLAKE2sObject;

#include "clinic/blake2s_impl.c.h"
Expand All @@ -51,17 +53,19 @@ typedef struct {
module _blake2
class _blake2.blake2s "BLAKE2sObject *" "&PyBlake2_BLAKE2sType"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=4b79d7ffe07286ce]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=d47b0527b39c673f]*/


static BLAKE2sObject *
new_BLAKE2sObject(PyTypeObject *type)
{
BLAKE2sObject *self;
self = (BLAKE2sObject *)type->tp_alloc(type, 0);
if (self != NULL) {
self->lock = NULL;
if (self == NULL) {
return NULL;
}
INIT_MUTEX(self);

return self;
}

Expand Down Expand Up @@ -93,7 +97,7 @@ py_blake2s_new_impl(PyTypeObject *type, PyObject *data, int digest_size,
int fanout, int depth, unsigned long leaf_size,
unsigned long long node_offset, int node_depth,
int inner_size, int last_node, int usedforsecurity)
/*[clinic end generated code: output=556181f73905c686 input=4dda87723f23abb0]*/
/*[clinic end generated code: output=32bfd8f043c6896f input=b947312abff46977]*/
{
BLAKE2sObject *self = NULL;
Py_buffer buf;
Expand Down Expand Up @@ -247,7 +251,7 @@ Return a copy of the hash object.

static PyObject *
_blake2_blake2s_copy_impl(BLAKE2sObject *self)
/*[clinic end generated code: output=5b90131c4eae275e input=0b9d44942f0fe4b2]*/
/*[clinic end generated code: output=ff6acee5f93656ae input=e383c2d199fd8a2e]*/
{
BLAKE2sObject *cpy;

Expand All @@ -272,24 +276,23 @@ Update this hash object's state with the provided bytes-like object.

static PyObject *
_blake2_blake2s_update(BLAKE2sObject *self, PyObject *data)
/*[clinic end generated code: output=757dc087fec37815 input=97500db2f9de4aaa]*/
/*[clinic end generated code: output=010dfcbe22654359 input=ffc4aa6a6a225d31]*/
{
Py_buffer buf;

GET_BUFFER_VIEW_OR_ERROUT(data, &buf);

if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE)
self->lock = PyThread_allocate_lock();

if (self->lock != NULL) {
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(self->lock, 1);
blake2s_update(&self->state, buf.buf, buf.len);
PyThread_release_lock(self->lock);
Py_END_ALLOW_THREADS
self->use_mutex = true;
if (buf.len >= HASHLIB_GIL_MINSIZE) {
Py_BEGIN_ALLOW_THREADS
PyMutex_Lock(&self->mutex);
blake2s_update(&self->state, buf.buf, buf.len);
PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
blake2s_update(&self->state, buf.buf, buf.len);
}

PyBuffer_Release(&buf);

Py_RETURN_NONE;
Expand All @@ -303,7 +306,7 @@ Return the digest value as a bytes object.

static PyObject *
_blake2_blake2s_digest_impl(BLAKE2sObject *self)
/*[clinic end generated code: output=40c566ca4bc6bc51 input=f41e0b8d6d937454]*/
/*[clinic end generated code: output=a5864660f4bfc61a input=7d21659e9c5fff02]*/
{
uint8_t digest[BLAKE2S_OUTBYTES];
blake2s_state state_cpy;
Expand All @@ -324,7 +327,7 @@ Return the digest value as a string of hexadecimal digits.

static PyObject *
_blake2_blake2s_hexdigest_impl(BLAKE2sObject *self)
/*[clinic end generated code: output=15153eb5e59c52eb input=c77a1321567e8952]*/
/*[clinic end generated code: output=b5598a87d8794a60 input=76930f6946351f56]*/
{
uint8_t digest[BLAKE2S_OUTBYTES];
blake2s_state state_cpy;
Expand Down Expand Up @@ -389,10 +392,6 @@ py_blake2s_dealloc(PyObject *self)
/* Try not to leave state in memory. */
secure_zero_memory(&obj->param, sizeof(obj->param));
secure_zero_memory(&obj->state, sizeof(obj->state));
if (obj->lock) {
PyThread_free_lock(obj->lock);
obj->lock = NULL;
}

PyTypeObject *type = Py_TYPE(self);
PyObject_Free(self);
Expand Down
44 changes: 15 additions & 29 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# define Py_BUILD_CORE_MODULE 1
#endif

#include <stdbool.h>
#include "Python.h"
#include "pycore_hashtable.h"
#include "pycore_pyhash.h" // _Py_HashBytes()
Expand Down Expand Up @@ -227,16 +228,16 @@ typedef struct {
PyObject_HEAD
EVP_MD_CTX *ctx; /* OpenSSL message digest context */
// Prevents undefined behavior via multiple threads entering the C API.
// The lock will be NULL before threaded access has been enabled.
PyThread_type_lock lock; /* OpenSSL context lock */
bool use_mutex;
PyMutex mutex;
} EVPobject;

typedef struct {
PyObject_HEAD
HMAC_CTX *ctx; /* OpenSSL hmac context */
// Prevents undefined behavior via multiple threads entering the C API.
// The lock will be NULL before threaded access has been enabled.
PyThread_type_lock lock; /* HMAC context lock */
bool use_mutex;
PyMutex mutex;
} HMACobject;

#include "clinic/_hashopenssl.c.h"
Expand Down Expand Up @@ -414,8 +415,7 @@ newEVPobject(PyTypeObject *type)
if (retval == NULL) {
return NULL;
}

retval->lock = NULL;
INIT_MUTEX(retval);

retval->ctx = EVP_MD_CTX_new();
if (retval->ctx == NULL) {
Expand Down Expand Up @@ -453,8 +453,6 @@ static void
EVP_dealloc(EVPobject *self)
{
PyTypeObject *tp = Py_TYPE(self);
if (self->lock != NULL)
PyThread_free_lock(self->lock);
EVP_MD_CTX_free(self->ctx);
PyObject_Free(self);
Py_DECREF(tp);
Expand Down Expand Up @@ -582,16 +580,11 @@ EVP_update(EVPobject *self, PyObject *obj)

GET_BUFFER_VIEW_OR_ERROUT(obj, &view);

if (self->lock == NULL && view.len >= HASHLIB_GIL_MINSIZE) {
self->lock = PyThread_allocate_lock();
/* fail? lock = NULL and we fail over to non-threaded code. */
}

if (self->lock != NULL) {
if (self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(self->lock, 1);
PyMutex_Lock(&self->mutex);
result = EVP_hash(self, view.buf, view.len);
PyThread_release_lock(self->lock);
PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
result = EVP_hash(self, view.buf, view.len);
Expand Down Expand Up @@ -1540,7 +1533,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj,
}

self->ctx = ctx;
self->lock = NULL;
self->mutex = (PyMutex){0};

if ((msg_obj != NULL) && (msg_obj != Py_None)) {
if (!_hmac_update(self, msg_obj))
Expand Down Expand Up @@ -1582,16 +1575,12 @@ _hmac_update(HMACobject *self, PyObject *obj)

GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);

if (self->lock == NULL && view.len >= HASHLIB_GIL_MINSIZE) {
self->lock = PyThread_allocate_lock();
/* fail? lock = NULL and we fail over to non-threaded code. */
}

if (self->lock != NULL) {
self->use_mutex = true;
if (view.len >= HASHLIB_GIL_MINSIZE) {
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(self->lock, 1);
PyMutex_Lock(&self->mutex);
r = HMAC_Update(self->ctx, (const unsigned char*)view.buf, view.len);
PyThread_release_lock(self->lock);
PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
r = HMAC_Update(self->ctx, (const unsigned char*)view.buf, view.len);
Expand Down Expand Up @@ -1633,7 +1622,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self)
return NULL;
}
retval->ctx = ctx;
retval->lock = NULL;
retval->mutex = (PyMutex){0};

return (PyObject *)retval;
}
Expand All @@ -1642,9 +1631,6 @@ static void
_hmac_dealloc(HMACobject *self)
{
PyTypeObject *tp = Py_TYPE(self);
if (self->lock != NULL) {
PyThread_free_lock(self->lock);
}
HMAC_CTX_free(self->ctx);
PyObject_Free(self);
Py_DECREF(tp);
Expand Down
34 changes: 26 additions & 8 deletions Modules/hashlib.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/* Common code for use by all hashlib related modules. */

#ifndef Py_BUILD_CORE
#define Py_BUILD_CORE
#endif

#include "pycore_lock.h" // PyMutex

/*
* Given a PyObject* obj, fill in the Py_buffer* viewp with the result
* of PyObject_GetBuffer. Sets an exception and issues the erraction
Expand Down Expand Up @@ -48,18 +54,30 @@

#include "pythread.h"
#define ENTER_HASHLIB(obj) \
if ((obj)->lock) { \
if (!PyThread_acquire_lock((obj)->lock, 0)) { \
Py_BEGIN_ALLOW_THREADS \
PyThread_acquire_lock((obj)->lock, 1); \
Py_END_ALLOW_THREADS \
} \
if ((obj)->use_mutex) { \
Py_BEGIN_ALLOW_THREADS \
PyMutex_Lock(&(obj)->mutex); \
Py_END_ALLOW_THREADS \
}
#define LEAVE_HASHLIB(obj) \
if ((obj)->lock) { \
PyThread_release_lock((obj)->lock); \
if ((obj)->use_mutex) { \
PyMutex_Unlock(&(obj)->mutex); \
}

#ifdef Py_NOGIL
#define INIT_MUTEX(obj) \
do { \
(obj)->mutex = (PyMutex){0}; \
(obj)->use_mutex = true; \
} while (0)
#else
#define INIT_MUTEX(obj) \
do { \
(obj)->mutex = (PyMutex){0}; \
(obj)->use_mutex = false; \
} while (0)
#endif

/* TODO(gpshead): We should make this a module or class attribute
* to allow the user to optimize based on the platform they're using. */
#define HASHLIB_GIL_MINSIZE 2048
Expand Down
Loading