Always use an individual lock on hash objects when in free-threaded builds.
Fixes #111916
--- /dev/null
+Make hashlib related modules thread-safe without the GIL
# define Py_BUILD_CORE_MODULE 1
#endif
+#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()
PyObject_HEAD
blake2b_param param;
blake2b_state state;
- PyThread_type_lock lock;
+ bool use_mutex;
+ PyMutex mutex;
} BLAKE2bObject;
#include "clinic/blake2b_impl.c.h"
{
BLAKE2bObject *self;
self = (BLAKE2bObject *)type->tp_alloc(type, 0);
- if (self != NULL) {
- self->lock = NULL;
+ if (self == NULL) {
+ return NULL;
}
+ HASHLIB_INIT_MUTEX(self);
+
return self;
}
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
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
+ }
+ if (self->use_mutex) {
+ 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;
/* 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);
# define Py_BUILD_CORE_MODULE 1
#endif
+#include <stdbool.h>
#include "Python.h"
#include "pycore_strhex.h" // _Py_strhex()
PyObject_HEAD
blake2s_param param;
blake2s_state state;
- PyThread_type_lock lock;
+ bool use_mutex;
+ PyMutex mutex;
} BLAKE2sObject;
#include "clinic/blake2s_impl.c.h"
{
BLAKE2sObject *self;
self = (BLAKE2sObject *)type->tp_alloc(type, 0);
- if (self != NULL) {
- self->lock = NULL;
+ if (self == NULL) {
+ return NULL;
}
+ HASHLIB_INIT_MUTEX(self);
+
return self;
}
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
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
+ }
+ if (self->use_mutex) {
+ 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;
/* 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);
# define Py_BUILD_CORE_MODULE 1
#endif
+#include <stdbool.h>
#include "Python.h"
#include "pycore_hashtable.h"
#include "pycore_pyhash.h" // _Py_HashBytes()
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; /* OpenSSL context lock */
} 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; /* HMAC context lock */
} HMACobject;
#include "clinic/_hashopenssl.c.h"
if (retval == NULL) {
return NULL;
}
-
- retval->lock = NULL;
+ HASHLIB_INIT_MUTEX(retval);
retval->ctx = EVP_MD_CTX_new();
if (retval->ctx == NULL) {
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);
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->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
-
- if (self->lock != NULL) {
+ if (self->use_mutex) {
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);
}
self->ctx = ctx;
- self->lock = NULL;
+ HASHLIB_INIT_MUTEX(self);
if ((msg_obj != NULL) && (msg_obj != Py_None)) {
if (!_hmac_update(self, msg_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->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
-
- if (self->lock != NULL) {
+ if (self->use_mutex) {
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);
return NULL;
}
retval->ctx = ctx;
- retval->lock = NULL;
+ HASHLIB_INIT_MUTEX(retval);
return (PyObject *)retval;
}
_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);
preserve
[clinic start generated code]*/
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+# include "pycore_gc.h" // PyGC_Head
+# include "pycore_runtime.h" // _Py_ID()
+#endif
+#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
+
PyDoc_STRVAR(MD5Type_copy__doc__,
"copy($self, /)\n"
"--\n"
"Return a copy of the hash object.");
#define MD5TYPE_COPY_METHODDEF \
- {"copy", (PyCFunction)(void(*)(void))MD5Type_copy, METH_METHOD|METH_FASTCALL|METH_KEYWORDS, MD5Type_copy__doc__},
+ {"copy", _PyCFunction_CAST(MD5Type_copy), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, MD5Type_copy__doc__},
static PyObject *
MD5Type_copy_impl(MD5object *self, PyTypeObject *cls);
"Return a new MD5 hash object; optionally initialized with a string.");
#define _MD5_MD5_METHODDEF \
- {"md5", (PyCFunction)(void(*)(void))_md5_md5, METH_VARARGS|METH_KEYWORDS, _md5_md5__doc__},
+ {"md5", _PyCFunction_CAST(_md5_md5), METH_FASTCALL|METH_KEYWORDS, _md5_md5__doc__},
static PyObject *
_md5_md5_impl(PyObject *module, PyObject *string, int usedforsecurity);
static PyObject *
-_md5_md5(PyObject *module, PyObject *args, PyObject *kwargs)
+_md5_md5(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- static char *_keywords[] = {"string", "usedforsecurity", NULL};
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 2
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(string), &_Py_ID(usedforsecurity), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"string", "usedforsecurity", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "md5",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
PyObject *string = NULL;
int usedforsecurity = 1;
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O$p:md5", _keywords,
- &string, &usedforsecurity))
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf);
+ if (!args) {
goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ string = args[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ usedforsecurity = PyObject_IsTrue(args[1]);
+ if (usedforsecurity < 0) {
+ goto exit;
+ }
+skip_optional_kwonly:
return_value = _md5_md5_impl(module, string, usedforsecurity);
exit:
return return_value;
}
-/*[clinic end generated code: output=81702ec915f36236 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=bfadda44914804a8 input=a9049054013a1b77]*/
/* Common code for use by all hashlib related modules. */
+#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
#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) { \
+ PyMutex_Lock(&(obj)->mutex); \
}
#define LEAVE_HASHLIB(obj) \
- if ((obj)->lock) { \
- PyThread_release_lock((obj)->lock); \
+ if ((obj)->use_mutex) { \
+ PyMutex_Unlock(&(obj)->mutex); \
}
+#ifdef Py_NOGIL
+#define HASHLIB_INIT_MUTEX(obj) \
+ do { \
+ (obj)->mutex = (PyMutex){0}; \
+ (obj)->use_mutex = true; \
+ } while (0)
+#else
+#define HASHLIB_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
/* MD5 objects */
-#ifndef _MSC_VER
-#include "pyconfig.h" // Py_NOGIL
-#endif
-
-#ifndef Py_NOGIL
-// Need limited C API version 3.12 for Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
-#define Py_LIMITED_API 0x030c0000
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
#endif
#include "Python.h"
typedef struct {
PyObject_HEAD
// 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;
+ bool use_mutex;
+ PyMutex mutex;
Hacl_Streaming_MD5_state *hash_state;
} MD5object;
newMD5object(MD5State * st)
{
MD5object *md5 = (MD5object *)PyObject_GC_New(MD5object, st->md5_type);
- md5->lock = NULL;
+ if (!md5) {
+ return NULL;
+ }
+ HASHLIB_INIT_MUTEX(md5);
+
PyObject_GC_Track(md5);
return md5;
}
MD5_dealloc(MD5object *ptr)
{
Hacl_Streaming_MD5_legacy_free(ptr->hash_state);
- if (ptr->lock != NULL) {
- PyThread_free_lock(ptr->lock);
- }
PyTypeObject *tp = Py_TYPE((PyObject*)ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->lock = PyThread_allocate_lock();
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
- if (self->lock != NULL) {
+ if (self->use_mutex) {
Py_BEGIN_ALLOW_THREADS
- PyThread_acquire_lock(self->lock, 1);
+ PyMutex_Lock(&self->mutex);
update(self->hash_state, buf.buf, buf.len);
- PyThread_release_lock(self->lock);
+ PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
update(self->hash_state, buf.buf, buf.len);
typedef struct {
PyObject_HEAD
// Prevents undefined behavior via multiple threads entering the C API.
- // The lock will be NULL before threaded access has been enabled.
+ bool use_mutex;
+ PyMutex mutex;
PyThread_type_lock lock;
Hacl_Streaming_SHA1_state *hash_state;
} SHA1object;
if (sha == NULL) {
return NULL;
}
- sha->lock = NULL;
+ HASHLIB_INIT_MUTEX(sha);
+
PyObject_GC_Track(sha);
return sha;
}
SHA1_dealloc(SHA1object *ptr)
{
Hacl_Streaming_SHA1_legacy_free(ptr->hash_state);
- if (ptr->lock != NULL) {
- PyThread_free_lock(ptr->lock);
- }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->lock = PyThread_allocate_lock();
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
- if (self->lock != NULL) {
+ if (self->use_mutex) {
Py_BEGIN_ALLOW_THREADS
- PyThread_acquire_lock(self->lock, 1);
+ PyMutex_Lock(&self->mutex);
update(self->hash_state, buf.buf, buf.len);
- PyThread_release_lock(self->lock);
+ PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
update(self->hash_state, buf.buf, buf.len);
PyObject_HEAD
int digestsize;
// 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;
+ bool use_mutex;
+ PyMutex mutex;
Hacl_Streaming_SHA2_state_sha2_256 *state;
} SHA256object;
PyObject_HEAD
int digestsize;
// 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;
+ bool use_mutex;
+ PyMutex mutex;
Hacl_Streaming_SHA2_state_sha2_512 *state;
} SHA512object;
if (!sha) {
return NULL;
}
- sha->lock = NULL;
+ HASHLIB_INIT_MUTEX(sha);
+
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
- sha->lock = NULL;
+ HASHLIB_INIT_MUTEX(sha);
+
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
- sha->lock = NULL;
+ HASHLIB_INIT_MUTEX(sha);
+
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
- sha->lock = NULL;
+ HASHLIB_INIT_MUTEX(sha);
+
PyObject_GC_Track(sha);
return sha;
}
SHA256_dealloc(SHA256object *ptr)
{
Hacl_Streaming_SHA2_free_256(ptr->state);
- if (ptr->lock != NULL) {
- PyThread_free_lock(ptr->lock);
- }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
SHA512_dealloc(SHA512object *ptr)
{
Hacl_Streaming_SHA2_free_512(ptr->state);
- if (ptr->lock != NULL) {
- PyThread_free_lock(ptr->lock);
- }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->lock = PyThread_allocate_lock();
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
- if (self->lock != NULL) {
+ if (self->use_mutex) {
Py_BEGIN_ALLOW_THREADS
- PyThread_acquire_lock(self->lock, 1);
+ PyMutex_Lock(&self->mutex);
update_256(self->state, buf.buf, buf.len);
- PyThread_release_lock(self->lock);
+ PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
update_256(self->state, buf.buf, buf.len);
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
- self->lock = PyThread_allocate_lock();
+ if (!self->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
- if (self->lock != NULL) {
+ if (self->use_mutex) {
Py_BEGIN_ALLOW_THREADS
- PyThread_acquire_lock(self->lock, 1);
+ PyMutex_Lock(&self->mutex);
update_512(self->state, buf.buf, buf.len);
- PyThread_release_lock(self->lock);
+ PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
update_512(self->state, buf.buf, buf.len);
typedef struct {
PyObject_HEAD
// 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;
+ bool use_mutex;
+ PyMutex mutex;
Hacl_Streaming_Keccak_state *hash_state;
} SHA3object;
if (newobj == NULL) {
return NULL;
}
- newobj->lock = NULL;
+ HASHLIB_INIT_MUTEX(newobj);
+
return newobj;
}
SHA3_dealloc(SHA3object *self)
{
Hacl_Streaming_Keccak_free(self->hash_state);
- if (self->lock != NULL) {
- PyThread_free_lock(self->lock);
- }
PyTypeObject *tp = Py_TYPE(self);
PyObject_Free(self);
Py_DECREF(tp);
/*[clinic end generated code: output=d3223352286ed357 input=a887f54dcc4ae227]*/
{
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->use_mutex && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->use_mutex = true;
}
- if (self->lock != NULL) {
+ if (self->use_mutex) {
Py_BEGIN_ALLOW_THREADS
- PyThread_acquire_lock(self->lock, 1);
+ PyMutex_Lock(&self->mutex);
sha3_update(self->hash_state, buf.buf, buf.len);
- PyThread_release_lock(self->lock);
+ PyMutex_Unlock(&self->mutex);
Py_END_ALLOW_THREADS
} else {
sha3_update(self->hash_state, buf.buf, buf.len);
}
+
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}