From eb3cb2ebdb64765d52c8fc3d77554b273490875c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:56:47 +0200 Subject: [PATCH 01/17] add helpers for setting exceptions --- Modules/_hashopenssl.c | 110 +++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 42821ebe9f6a54..174855d020e672 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -311,8 +311,9 @@ class _hashlib.HMAC "HMACobject *" "((_hashlibstate *)PyModule_GetState(module)) /* Set an exception of given type using the given OpenSSL error code. */ static void -set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) +set_ssl_exception_from_errcode(PyObject *exc_type, unsigned long errcode) { + assert(exc_type != NULL); assert(errcode != 0); /* ERR_ERROR_STRING(3) ensures that the messages below are ASCII */ @@ -321,13 +322,29 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) const char *reason = ERR_reason_error_string(errcode); if (lib && func) { - PyErr_Format(exc, "[%s: %s] %s", lib, func, reason); + PyErr_Format(exc_type, "[%s: %s] %s", lib, func, reason); } else if (lib) { - PyErr_Format(exc, "[%s] %s", lib, reason); + PyErr_Format(exc_type, "[%s] %s", lib, reason); } else { - PyErr_SetString(exc, reason); + PyErr_SetString(exc_type, reason); + } +} + +/* + * Get an appropriate exception type for the given OpenSSL error code. + * + * The exception type depends on the error code reason. + */ +static PyObject * +get_smart_ssl_exception_type(unsigned long errcode, PyObject *default_exc_type) +{ + switch (ERR_GET_REASON(errcode)) { + case ERR_R_MALLOC_FAILURE: + return PyExc_MemoryError; + default: + return default_exc_type; } } @@ -335,36 +352,99 @@ set_ssl_exception_from_errcode(PyObject *exc, unsigned long errcode) * Set an exception of given type. * * By default, the exception's message is constructed by using the last SSL - * error that occurred. If no error occurred, the 'fallback_format' is used - * to create a C-style formatted fallback message. + * error that occurred. If no error occurred, the 'fallback_message' is used + * to create an exception message. */ static void -raise_ssl_error(PyObject *exc, const char *fallback_format, ...) +raise_ssl_error(PyObject *exc_type, const char *fallback_message) +{ + assert(fallback_message != NULL); + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + PyErr_SetString(exc_type, fallback_message); + } +} + +/* Same as raise_ssl_error() with smart exception types. */ +static void +raise_smart_ssl_error(PyObject *exc_type, const char *fallback_message) +{ + assert(fallback_message != NULL); + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + exc_type = get_smart_ssl_exception_type(errcode, exc_type); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + PyErr_SetString(exc_type, fallback_message); + } +} + +/* Same as raise_ssl_error() but with a C-style formatted message. */ +static void +raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) { assert(fallback_format != NULL); unsigned long errcode = ERR_peek_last_error(); if (errcode) { ERR_clear_error(); - set_ssl_exception_from_errcode(exc, errcode); + set_ssl_exception_from_errcode(exc_type, errcode); } else { va_list vargs; va_start(vargs, fallback_format); - PyErr_FormatV(exc, fallback_format, vargs); + PyErr_FormatV(exc_type, fallback_format, vargs); + va_end(vargs); + } +} + +/* Same as raise_smart_ssl_error() but with a C-style formatted message. */ +static void +raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) +{ + unsigned long errcode = ERR_peek_last_error(); + if (errcode) { + ERR_clear_error(); + exc_type = get_smart_ssl_exception_type(errcode, exc_type); + set_ssl_exception_from_errcode(exc_type, errcode); + } + else { + va_list vargs; + va_start(vargs, fallback_format); + PyErr_FormatV(exc_type, fallback_format, vargs); va_end(vargs); } } /* - * Set an exception with a generic default message after an error occurred. - * - * It can also be used without previous calls to SSL built-in functions, - * in which case a generic error message is provided. + * Raise a ValueError with a default message after an error occurred. + * It can also be used without previous calls to SSL built-in functions. */ static inline void -notify_ssl_error_occurred(void) +notify_ssl_error_occurred(const char *message) +{ + raise_ssl_error(PyExc_ValueError, message); +} + +/* Same as notify_ssl_error_occurred() for failed OpenSSL functions. */ +static inline void +notify_ssl_error_occurred_in(const char *funcname) +{ + raise_ssl_error_f(PyExc_ValueError, + "error in OpenSSL function: %s", funcname); +} + +/* Same as notify_smart_ssl_error_occurred() for failed OpenSSL functions. */ +static inline void +notify_smart_ssl_error_occurred_in(const char *funcname) { - raise_ssl_error(PyExc_ValueError, "no reason supplied"); + raise_smart_ssl_error_f(PyExc_ValueError, + "error in OpenSSL function %s", funcname); } /* LCOV_EXCL_STOP */ From e242865b95dbfd537a289dd9234eef3987232206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:57:17 +0200 Subject: [PATCH 02/17] refactor `get_openssl_evp_md_by_utf8name` error branches --- Modules/_hashopenssl.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 174855d020e672..869651f336420a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -487,8 +487,7 @@ static PY_EVP_MD * get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, Py_hash_type py_ht) { - PY_EVP_MD *digest = NULL; - PY_EVP_MD *other_digest = NULL; + PY_EVP_MD *digest = NULL, *other_digest = NULL; _hashlibstate *state = get_hashlib_state(module); py_hashentry_t *entry = (py_hashentry_t *)_Py_hashtable_get( state->hashtable, (const void*)name @@ -522,15 +521,16 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, #endif } break; + default: + goto invalid_hash_type; } // if another thread same thing at same time make sure we got same ptr assert(other_digest == NULL || other_digest == digest); - if (digest != NULL) { - if (other_digest == NULL) { - PY_EVP_MD_up_ref(digest); - } + if (digest != NULL && other_digest == NULL) { + PY_EVP_MD_up_ref(digest); } - } else { + } + else { // Fall back for looking up an unindexed OpenSSL specific name. switch (py_ht) { case Py_ht_evp: @@ -541,14 +541,21 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, case Py_ht_evp_nosecurity: digest = PY_EVP_MD_fetch(name, "-fips"); break; + default: + goto invalid_hash_type; } } if (digest == NULL) { - raise_ssl_error(state->unsupported_digestmod_error, - "unsupported hash type %s", name); + raise_ssl_error_f(state->unsupported_digestmod_error, + "EVP_MD_fetch: cannot fetch from %s", name); return NULL; } return digest; + +invalid_hash_type: + assert(digest == NULL); + PyErr_Format(PyExc_SystemError, "unsupported hash type %d", py_ht); + return NULL; } /* Get digest EVP_MD from object From ac6dea4dcd19d28282f3cfe37b315adb17d0dd0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:08:12 +0200 Subject: [PATCH 03/17] refactor `HASH.{digest,hexdigest}` computations --- Modules/_hashopenssl.c | 80 +++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 869651f336420a..e7c1b13f29236a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -683,6 +683,30 @@ _hashlib_HASH_copy_impl(HASHobject *self) return (PyObject *)newobj; } +static Py_ssize_t +_hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + return -1; + } + if (_hashlib_HASH_copy_locked(self, ctx) < 0) { + goto error; + } + Py_ssize_t digest_size = EVP_MD_CTX_size(ctx); + if (!EVP_DigestFinal(ctx, digest, NULL)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinal)); + goto error; + } + EVP_MD_CTX_free(ctx); + return digest_size; + +error: + EVP_MD_CTX_free(ctx); + return -1; +} + /*[clinic input] _hashlib.HASH.digest @@ -694,32 +718,8 @@ _hashlib_HASH_digest_impl(HASHobject *self) /*[clinic end generated code: output=3fc6f9671d712850 input=d8d528d6e50af0de]*/ { unsigned char digest[EVP_MAX_MD_SIZE]; - EVP_MD_CTX *temp_ctx; - PyObject *retval; - unsigned int digest_size; - - temp_ctx = EVP_MD_CTX_new(); - if (temp_ctx == NULL) { - PyErr_NoMemory(); - return NULL; - } - - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { - goto error; - } - digest_size = EVP_MD_CTX_size(temp_ctx); - if (!EVP_DigestFinal(temp_ctx, digest, NULL)) { - goto error; - } - - retval = PyBytes_FromStringAndSize((const char *)digest, digest_size); - EVP_MD_CTX_free(temp_ctx); - return retval; - -error: - EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); - return NULL; + Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest); + return n < 0 ? NULL : PyBytes_FromStringAndSize((const char *)digest, n); } /*[clinic input] @@ -733,32 +733,8 @@ _hashlib_HASH_hexdigest_impl(HASHobject *self) /*[clinic end generated code: output=1b8e60d9711e7f4d input=ae7553f78f8372d8]*/ { unsigned char digest[EVP_MAX_MD_SIZE]; - EVP_MD_CTX *temp_ctx; - unsigned int digest_size; - - temp_ctx = EVP_MD_CTX_new(); - if (temp_ctx == NULL) { - PyErr_NoMemory(); - return NULL; - } - - /* Get the raw (binary) digest value */ - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { - goto error; - } - digest_size = EVP_MD_CTX_size(temp_ctx); - if (!EVP_DigestFinal(temp_ctx, digest, NULL)) { - goto error; - } - - EVP_MD_CTX_free(temp_ctx); - - return _Py_strhex((const char *)digest, (Py_ssize_t)digest_size); - -error: - EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); - return NULL; + Py_ssize_t n = _hashlib_HASH_digest_compute(self, digest); + return n < 0 ? NULL : _Py_strhex((const char *)digest, n); } /*[clinic input] From 4bc2808cad2148ed151eb3127aa224b8b8cf7e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:09:24 +0200 Subject: [PATCH 04/17] refactor `_hashlib_HASH_copy_locked` and `locked_HMAC_CTX_copy` --- Modules/_hashopenssl.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index e7c1b13f29236a..997e9d2f778a00 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -655,7 +655,11 @@ _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) ENTER_HASHLIB(self); result = EVP_MD_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); - return result; + if (result == 0) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); + return -1; + } + return 0; } /* External methods for a hash object */ @@ -675,7 +679,7 @@ _hashlib_HASH_copy_impl(HASHobject *self) if ((newobj = new_hash_object(Py_TYPE(self))) == NULL) return NULL; - if (!_hashlib_HASH_copy_locked(self, newobj->ctx)) { + if (_hashlib_HASH_copy_locked(self, newobj->ctx) < 0) { Py_DECREF(newobj); notify_ssl_error_occurred(); return NULL; @@ -899,7 +903,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { + if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) { goto error; } if (!EVP_DigestFinalXOF(temp_ctx, @@ -949,7 +953,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) } /* Get the raw (binary) digest value */ - if (!_hashlib_HASH_copy_locked(self, temp_ctx)) { + if (_hashlib_HASH_copy_locked(self, temp_ctx) < 0) { goto error; } if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) { @@ -1736,7 +1740,11 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self) ENTER_HASHLIB(self); result = HMAC_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); - return result; + if (result == 0) { + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); + return -1; + } + return 0; } /* returning 0 means that an error occurred and an exception is set */ @@ -1805,9 +1813,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) if (ctx == NULL) { return PyErr_NoMemory(); } - if (!locked_HMAC_CTX_copy(ctx, self)) { + if (locked_HMAC_CTX_copy(ctx, self) < 0) { HMAC_CTX_free(ctx); - notify_ssl_error_occurred(); return NULL; } @@ -1879,9 +1886,8 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) (void)PyErr_NoMemory(); return 0; } - if (!locked_HMAC_CTX_copy(temp_ctx, self)) { + if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { HMAC_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return 0; } int r = HMAC_Final(temp_ctx, buf, &len); From 14b87df1b0f7c63f9555b3e265baf085f11a787c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:09:49 +0200 Subject: [PATCH 05/17] set remaining exceptions after an OpenSSL failure --- Modules/_hashopenssl.c | 49 ++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 997e9d2f778a00..c0c5dbb7927427 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -439,7 +439,7 @@ notify_ssl_error_occurred_in(const char *funcname) "error in OpenSSL function: %s", funcname); } -/* Same as notify_smart_ssl_error_occurred() for failed OpenSSL functions. */ +/* Same as notify_ssl_error_occurred() with smart exception types. */ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { @@ -609,7 +609,7 @@ new_hash_object(PyTypeObject *type) retval->ctx = EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -627,7 +627,7 @@ _hashlib_HASH_hash(HASHobject *self, const void *vp, Py_ssize_t len) else process = Py_SAFE_DOWNCAST(len, Py_ssize_t, unsigned int); if (!EVP_DigestUpdate(self->ctx, (const void*)cp, process)) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestUpdate)); return -1; } len -= process; @@ -681,7 +681,6 @@ _hashlib_HASH_copy_impl(HASHobject *self) if (_hashlib_HASH_copy_locked(self, newobj->ctx) < 0) { Py_DECREF(newobj); - notify_ssl_error_occurred(); return NULL; } return (PyObject *)newobj; @@ -692,7 +691,7 @@ _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return -1; } if (_hashlib_HASH_copy_locked(self, ctx) < 0) { @@ -809,7 +808,7 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) HASHobject *self = HASHobject_CAST(op); const EVP_MD *md = EVP_MD_CTX_md(self->ctx); if (md == NULL) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred("missing EVP_MD"); return NULL; } return get_openssl_evp_md_name(md); @@ -899,7 +898,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) temp_ctx = EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -910,6 +909,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) (unsigned char*)PyBytes_AS_STRING(retval), length)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF)); goto error; } @@ -919,7 +919,6 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) error: Py_DECREF(retval); EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return NULL; } @@ -948,7 +947,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) temp_ctx = EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -957,6 +956,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) goto error; } if (!EVP_DigestFinalXOF(temp_ctx, digest, length)) { + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestFinalXOF)); goto error; } @@ -969,7 +969,6 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) error: PyMem_Free(digest); EVP_MD_CTX_free(temp_ctx); - notify_ssl_error_occurred(); return NULL; } @@ -1073,7 +1072,7 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj, int result = EVP_DigestInit_ex(self->ctx, digest, NULL); if (!result) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_DigestInit_ex)); Py_CLEAR(self); goto exit; } @@ -1482,7 +1481,7 @@ pbkdf2_hmac_impl(PyObject *module, const char *hash_name, if (!retval) { Py_CLEAR(key_obj); - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(PKCS5_PBKDF2_HMAC)); goto end; } @@ -1580,7 +1579,7 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, if (!retval) { Py_CLEAR(key_obj); - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_PBE_scrypt)); return NULL; } return key_obj; @@ -1637,7 +1636,7 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, PY_EVP_MD_free(evp); if (result == NULL) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC)); return NULL; } return PyBytes_FromStringAndSize((const char*)md, md_len); @@ -1698,14 +1697,14 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, ctx = HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); - PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); goto error; } r = HMAC_Init_ex(ctx, key->buf, (int)key->len, digest, NULL /* impl */); PY_EVP_MD_free(digest); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Init_ex)); goto error; } @@ -1791,7 +1790,7 @@ _hmac_update(HMACobject *self, PyObject *obj) PyBuffer_Release(&view); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Update)); return 0; } return 1; @@ -1811,7 +1810,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { - return PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + return NULL; } if (locked_HMAC_CTX_copy(ctx, self) < 0) { HMAC_CTX_free(ctx); @@ -1883,7 +1883,7 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { HMAC_CTX *temp_ctx = HMAC_CTX_new(); if (temp_ctx == NULL) { - (void)PyErr_NoMemory(); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return 0; } if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { @@ -1893,7 +1893,7 @@ _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) int r = HMAC_Final(temp_ctx, buf, &len); HMAC_CTX_free(temp_ctx); if (r == 0) { - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_Final)); return 0; } return 1; @@ -2113,16 +2113,13 @@ _hashlib_get_fips_mode_impl(PyObject *module) #else ERR_clear_error(); int result = FIPS_mode(); - if (result == 0) { + if (result == 0 && ERR_peek_last_error()) { // "If the library was built without support of the FIPS Object Module, // then the function will return 0 with an error code of // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." // But 0 is also a valid result value. - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { - notify_ssl_error_occurred(); - return -1; - } + notify_ssl_error_occurred(); + return -1; } return result; #endif From e5b2ef301bdc5177ff464ba09cf2c96e0bdb4c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:14:52 +0200 Subject: [PATCH 06/17] blurb --- .../next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst new file mode 100644 index 00000000000000..4ca59812dbe9dd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst @@ -0,0 +1,2 @@ +:mod:`hashlib`: improve exception messages when an OpenSSL function failed. +Patch by Bénédikt Tran. From 5b412423de05bcf0df52256841355fbc15811133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:41:51 +0200 Subject: [PATCH 07/17] wrap OpenSSL context allocators --- Modules/_hashopenssl.c | 45 +++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index c0c5dbb7927427..c64d686b773417 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -597,6 +597,20 @@ get_openssl_evp_md(PyObject *module, PyObject *digestmod, return get_openssl_evp_md_by_utf8name(module, name, py_ht); } +// --- OpenSSL HASH wrappers -------------------------------------------------- + +static EVP_MD_CTX * +py_EVP_MD_CTX_new(void) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + } + return ctx; +} + +// --- HASH interface --------------------------------------------------------- + static HASHobject * new_hash_object(PyTypeObject *type) { @@ -606,10 +620,9 @@ new_hash_object(PyTypeObject *type) } HASHLIB_INIT_MUTEX(retval); - retval->ctx = EVP_MD_CTX_new(); + retval->ctx = py_EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -689,9 +702,8 @@ _hashlib_HASH_copy_impl(HASHobject *self) static Py_ssize_t _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_MD_CTX *ctx = py_EVP_MD_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return -1; } if (_hashlib_HASH_copy_locked(self, ctx) < 0) { @@ -895,10 +907,9 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = EVP_MD_CTX_new(); + temp_ctx = py_EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -944,10 +955,9 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = EVP_MD_CTX_new(); + temp_ctx = py_EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); return NULL; } @@ -1645,6 +1655,16 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /* OpenSSL-based HMAC implementation */ +static HMAC_CTX * +py_HMAC_CTX_new(void) +{ + HMAC_CTX *ctx = HMAC_CTX_new(); + if (ctx == NULL) { + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + } + return ctx; +} + static int _hmac_update(HMACobject*, PyObject*); static const EVP_MD * @@ -1694,10 +1714,9 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } - ctx = HMAC_CTX_new(); + ctx = py_HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); goto error; } @@ -1808,9 +1827,8 @@ _hashlib_HMAC_copy_impl(HMACobject *self) { HMACobject *retval; - HMAC_CTX *ctx = HMAC_CTX_new(); + HMAC_CTX *ctx = py_HMAC_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return NULL; } if (locked_HMAC_CTX_copy(ctx, self) < 0) { @@ -1881,9 +1899,8 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg) static int _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { - HMAC_CTX *temp_ctx = HMAC_CTX_new(); + HMAC_CTX *temp_ctx = py_HMAC_CTX_new(); if (temp_ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); return 0; } if (locked_HMAC_CTX_copy(temp_ctx, self) < 0) { From 2a8cf5555430ee3dfd475e23a02e39748c26beb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:50:12 +0200 Subject: [PATCH 08/17] fix compilation, remove unused functions and update messages --- Modules/_hashopenssl.c | 36 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index c64d686b773417..cb083e0449e0f8 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -369,22 +369,6 @@ raise_ssl_error(PyObject *exc_type, const char *fallback_message) } } -/* Same as raise_ssl_error() with smart exception types. */ -static void -raise_smart_ssl_error(PyObject *exc_type, const char *fallback_message) -{ - assert(fallback_message != NULL); - unsigned long errcode = ERR_peek_last_error(); - if (errcode) { - ERR_clear_error(); - exc_type = get_smart_ssl_exception_type(errcode, exc_type); - set_ssl_exception_from_errcode(exc_type, errcode); - } - else { - PyErr_SetString(exc_type, fallback_message); - } -} - /* Same as raise_ssl_error() but with a C-style formatted message. */ static void raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) @@ -403,7 +387,7 @@ raise_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) } } -/* Same as raise_smart_ssl_error() but with a C-style formatted message. */ +/* Same as raise_ssl_error_f() with smart exception types. */ static void raise_smart_ssl_error_f(PyObject *exc_type, const char *fallback_format, ...) { @@ -439,7 +423,7 @@ notify_ssl_error_occurred_in(const char *funcname) "error in OpenSSL function: %s", funcname); } -/* Same as notify_ssl_error_occurred() with smart exception types. */ +/* Same as notify_ssl_error_occurred_in() with smart exception types. */ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { @@ -669,7 +653,7 @@ _hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p) result = EVP_MD_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); if (result == 0) { - notify_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy)); return -1; } return 0; @@ -820,7 +804,7 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) HASHobject *self = HASHobject_CAST(op); const EVP_MD *md = EVP_MD_CTX_md(self->ctx); if (md == NULL) { - notify_ssl_error_occurred("missing EVP_MD"); + notify_ssl_error_occurred("missing EVP_MD for HASH context"); return NULL; } return get_openssl_evp_md_name(md); @@ -1567,8 +1551,8 @@ _hashlib_scrypt_impl(PyObject *module, Py_buffer *password, Py_buffer *salt, /* let OpenSSL validate the rest */ retval = EVP_PBE_scrypt(NULL, 0, NULL, 0, n, r, p, maxmem, NULL, 0); if (!retval) { - raise_ssl_error(PyExc_ValueError, - "Invalid parameter combination for n, r, p, maxmem."); + notify_ssl_error_occurred( + "Invalid parameter combination for n, r, p, maxmem."); return NULL; } @@ -1672,7 +1656,7 @@ _hashlib_hmac_get_md(HMACobject *self) { const EVP_MD *md = HMAC_CTX_get_md(self->ctx); if (md == NULL) { - raise_ssl_error(PyExc_ValueError, "missing EVP_MD for HMAC context"); + notify_ssl_error_occurred("missing EVP_MD for HMAC context"); } return md; } @@ -1759,7 +1743,7 @@ locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self) result = HMAC_CTX_copy(new_ctx_p, self->ctx); LEAVE_HASHLIB(self); if (result == 0) { - notify_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); + notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy)); return -1; } return 0; @@ -1776,7 +1760,7 @@ _hashlib_hmac_digest_size(HMACobject *self) unsigned int digest_size = EVP_MD_size(md); assert(digest_size <= EVP_MAX_MD_SIZE); if (digest_size == 0) { - raise_ssl_error(PyExc_ValueError, "invalid digest size"); + notify_ssl_error_occurred("invalid digest size"); } return digest_size; } @@ -2135,7 +2119,7 @@ _hashlib_get_fips_mode_impl(PyObject *module) // then the function will return 0 with an error code of // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." // But 0 is also a valid result value. - notify_ssl_error_occurred(); + notify_ssl_error_occurred_in(Py_STRINGIFY(FIPS_mode)); return -1; } return result; From ea2ce0eafc3cb4c1d5b883e5474e02efb0116cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:53:12 +0200 Subject: [PATCH 09/17] update wrappers --- Modules/_hashopenssl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index cb083e0449e0f8..848948c30a67cf 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -583,8 +583,9 @@ get_openssl_evp_md(PyObject *module, PyObject *digestmod, // --- OpenSSL HASH wrappers -------------------------------------------------- +/* Thin wrapper around EVP_MD_CTX_new() which sets an exception on failure. */ static EVP_MD_CTX * -py_EVP_MD_CTX_new(void) +py_wrapper_EVP_MD_CTX_new(void) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { @@ -604,7 +605,7 @@ new_hash_object(PyTypeObject *type) } HASHLIB_INIT_MUTEX(retval); - retval->ctx = py_EVP_MD_CTX_new(); + retval->ctx = py_wrapper_EVP_MD_CTX_new(); if (retval->ctx == NULL) { Py_DECREF(retval); return NULL; @@ -686,7 +687,7 @@ _hashlib_HASH_copy_impl(HASHobject *self) static Py_ssize_t _hashlib_HASH_digest_compute(HASHobject *self, unsigned char *digest) { - EVP_MD_CTX *ctx = py_EVP_MD_CTX_new(); + EVP_MD_CTX *ctx = py_wrapper_EVP_MD_CTX_new(); if (ctx == NULL) { return -1; } @@ -891,7 +892,7 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = py_EVP_MD_CTX_new(); + temp_ctx = py_wrapper_EVP_MD_CTX_new(); if (temp_ctx == NULL) { Py_DECREF(retval); return NULL; @@ -939,7 +940,7 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length) return NULL; } - temp_ctx = py_EVP_MD_CTX_new(); + temp_ctx = py_wrapper_EVP_MD_CTX_new(); if (temp_ctx == NULL) { PyMem_Free(digest); return NULL; @@ -1639,8 +1640,9 @@ _hashlib_hmac_singleshot_impl(PyObject *module, Py_buffer *key, /* OpenSSL-based HMAC implementation */ +/* Thin wrapper around HMAC_CTX_new() which sets an exception on failure. */ static HMAC_CTX * -py_HMAC_CTX_new(void) +py_openssl_wrapper_HMAC_CTX_new(void) { HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { @@ -1698,7 +1700,7 @@ _hashlib_hmac_new_impl(PyObject *module, Py_buffer *key, PyObject *msg_obj, return NULL; } - ctx = py_HMAC_CTX_new(); + ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { PY_EVP_MD_free(digest); goto error; @@ -1811,7 +1813,7 @@ _hashlib_HMAC_copy_impl(HMACobject *self) { HMACobject *retval; - HMAC_CTX *ctx = py_HMAC_CTX_new(); + HMAC_CTX *ctx = py_openssl_wrapper_HMAC_CTX_new(); if (ctx == NULL) { return NULL; } @@ -1883,7 +1885,7 @@ _hashlib_HMAC_update_impl(HMACobject *self, PyObject *msg) static int _hmac_digest(HMACobject *self, unsigned char *buf, unsigned int len) { - HMAC_CTX *temp_ctx = py_HMAC_CTX_new(); + HMAC_CTX *temp_ctx = py_openssl_wrapper_HMAC_CTX_new(); if (temp_ctx == NULL) { return 0; } From 43739c514d70fe80045f11530750b93104643a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:57:18 +0200 Subject: [PATCH 10/17] align exception messages --- Modules/_hashopenssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 848948c30a67cf..00889c5e72f4f9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -420,7 +420,7 @@ static inline void notify_ssl_error_occurred_in(const char *funcname) { raise_ssl_error_f(PyExc_ValueError, - "error in OpenSSL function: %s", funcname); + "error in OpenSSL function %s()", funcname); } /* Same as notify_ssl_error_occurred_in() with smart exception types. */ @@ -428,7 +428,7 @@ static inline void notify_smart_ssl_error_occurred_in(const char *funcname) { raise_smart_ssl_error_f(PyExc_ValueError, - "error in OpenSSL function %s", funcname); + "error in OpenSSL function %s()", funcname); } /* LCOV_EXCL_STOP */ From e226431d0ba1b228318b37e0de42fc6d5f5be149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:13:16 +0200 Subject: [PATCH 11/17] context allocators can only set an ERR_R_MALLOC_FAILURE error --- Modules/_hashopenssl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 00889c5e72f4f9..b81f668b2aa453 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -589,7 +589,8 @@ py_wrapper_EVP_MD_CTX_new(void) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_new)); + PyErr_NoMemory(); + return NULL; } return ctx; } @@ -1646,7 +1647,8 @@ py_openssl_wrapper_HMAC_CTX_new(void) { HMAC_CTX *ctx = HMAC_CTX_new(); if (ctx == NULL) { - notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_new)); + PyErr_NoMemory(); + return NULL; } return ctx; } From 7201e0ad23dabdc41a2546d263c250a0c01bf74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:22:21 +0200 Subject: [PATCH 12/17] update exception message for `get_openssl_evp_md_by_utf8name` --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b81f668b2aa453..df96f70936f01f 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -531,7 +531,7 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, } if (digest == NULL) { raise_ssl_error_f(state->unsupported_digestmod_error, - "EVP_MD_fetch: cannot fetch from %s", name); + "unsupported digest name: %s", name); return NULL; } return digest; From f8c2aed03f46fd460230ec51830aed690efdfcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:37:43 +0200 Subject: [PATCH 13/17] refactor logic for mapping NIDs to EVP_MD objects --- Modules/_hashopenssl.c | 169 ++++++++++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 62 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 42821ebe9f6a54..b553a681f5820a 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -368,41 +368,83 @@ notify_ssl_error_occurred(void) } /* LCOV_EXCL_STOP */ -static const char * -get_openssl_evp_md_utf8name(const EVP_MD *md) -{ - assert(md != NULL); - int nid = EVP_MD_nid(md); - const char *name = NULL; - const py_hashentry_t *h; +/* + * OpenSSL provides a way to go from NIDs to digest names for hash functions + * but lacks this granularity for MAC objects where it is not possible to get + * the underlying digest name (only the block size and digest size are allowed + * to be recovered). + * + * In addition, OpenSSL aliases pollute the list of known digest names + * as OpenSSL appears to have its own definition of alias. In particular, + * the resulting list still contains duplicate and alternate names for several + * algorithms. + * + * Therefore, digest names, whether they are used by hash functions or HMAC, + * are handled through EVP_MD objects or directly by using some NID. + */ - for (h = py_hashes; h->py_name != NULL; h++) { +/* Get a cached entry by OpenSSL NID. */ +static const py_hashentry_t * +get_hashentry_by_nid(int nid) +{ + for (const py_hashentry_t *h = py_hashes; h->py_name != NULL; h++) { if (h->ossl_nid == nid) { - name = h->py_name; - break; + return h; } } + return NULL; +} + +/* + * Convert the NID to a string via OBJ_nid2_*() functions. + * + * If 'nid' cannot be resolved, set an exception and return NULL. + */ +static const char * +get_asn1_utf8name_by_nid(int nid) +{ + const char *name = OBJ_nid2ln(nid); if (name == NULL) { - /* Ignore aliased names and only use long, lowercase name. The aliases - * pollute the list and OpenSSL appears to have its own definition of - * alias as the resulting list still contains duplicate and alternate - * names for several algorithms. - */ - name = OBJ_nid2ln(nid); - if (name == NULL) - name = OBJ_nid2sn(nid); + // In OpenSSL 3.0 and later, OBJ_nid*() are thread-safe and may raise. + assert(ERR_peek_last_error() != 0); + if (ERR_GET_REASON(ERR_peek_last_error()) != OBJ_R_UNKNOWN_NID) { + notify_ssl_error_occurred(); + return NULL; + } + // fallback to short name and unconditionally propagate errors + name = OBJ_nid2sn(nid); + if (name == NULL) { + raise_ssl_error(PyExc_ValueError, "cannot resolve NID %d", nid); + } } return name; } -static PyObject * -get_openssl_evp_md_name(const EVP_MD *md) +/* + * Convert the NID to an OpenSSL digest name. + * + * On error, set an exception and return NULL. + */ +static const char * +get_hashlib_utf8name_by_nid(int nid) +{ + const py_hashentry_t *e = get_hashentry_by_nid(nid); + return e ? e->py_name : get_asn1_utf8name_by_nid(nid); +} + +/* Same as get_hashlib_utf8name_by_nid() but using an EVP_MD object. */ +static const char * +get_hashlib_utf8name_by_evp_md(const EVP_MD *md) { - const char *name = get_openssl_evp_md_utf8name(md); - return PyUnicode_FromString(name); + assert(md != NULL); + return get_hashlib_utf8name_by_nid(EVP_MD_nid(md)); } -/* Get EVP_MD by HID and purpose */ +/* + * Get a new reference to an EVP_MD object described by name and purpose. + * + * If 'name' is an OpenSSL indexed name, the return value is cached. + */ static PY_EVP_MD * get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, Py_hash_type py_ht) @@ -464,6 +506,7 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, } } if (digest == NULL) { + // NOTE(picnixz): report hash type value instead of name raise_ssl_error(state->unsupported_digestmod_error, "unsupported hash type %s", name); return NULL; @@ -471,42 +514,46 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, return digest; } -/* Get digest EVP_MD from object +/* + * Raise an exception indicating that 'digestmod' is not supported. + */ +static void +raise_unsupported_digestmod_error(PyObject *module, PyObject *digestmod) +{ + _hashlibstate *state = get_hashlib_state(module); + PyErr_Format(state->unsupported_digestmod_error, + "Unsupported digestmod %R", digestmod); +} + +/* + * Get a new reference to an EVP_MD described by 'digestmod' and purpose. + * + * On error, set an exception and return NULL. * - * * string - * * _hashopenssl builtin function + * Parameters * - * on error returns NULL with exception set. + * digestmod A digest name or a _hashlib.openssl_* function + * py_ht The message digest purpose. */ static PY_EVP_MD * -get_openssl_evp_md(PyObject *module, PyObject *digestmod, - Py_hash_type py_ht) +get_openssl_evp_md(PyObject *module, PyObject *digestmod, Py_hash_type py_ht) { - PyObject *name_obj = NULL; const char *name; - if (PyUnicode_Check(digestmod)) { - name_obj = digestmod; - } else { - _hashlibstate *state = get_hashlib_state(module); - // borrowed ref - name_obj = PyDict_GetItemWithError(state->constructs, digestmod); + name = PyUnicode_AsUTF8(digestmod); } - if (name_obj == NULL) { - if (!PyErr_Occurred()) { - _hashlibstate *state = get_hashlib_state(module); - PyErr_Format( - state->unsupported_digestmod_error, - "Unsupported digestmod %R", digestmod); - } - return NULL; + else { + PyObject *dict = get_hashlib_state(module)->constructs; + assert(dict != NULL); + PyObject *borrowed_ref = PyDict_GetItemWithError(dict, digestmod); + name = borrowed_ref == NULL ? NULL : PyUnicode_AsUTF8(borrowed_ref); } - - name = PyUnicode_AsUTF8(name_obj); if (name == NULL) { + if (!PyErr_Occurred()) { + raise_unsupported_digestmod_error(module, digestmod); + } return NULL; } - return get_openssl_evp_md_by_utf8name(module, name, py_ht); } @@ -745,7 +792,9 @@ _hashlib_HASH_get_name(PyObject *op, void *Py_UNUSED(closure)) notify_ssl_error_occurred(); return NULL; } - return get_openssl_evp_md_name(md); + const char *name = get_hashlib_utf8name_by_evp_md(md); + assert(name != NULL || PyErr_Occurred()); + return name == NULL ? NULL : PyUnicode_FromString(name); } static PyGetSetDef HASH_getsets[] = { @@ -1775,20 +1824,15 @@ _hmac_dealloc(PyObject *op) static PyObject * _hmac_repr(PyObject *op) { + const char *digest_name; HMACobject *self = HMACobject_CAST(op); const EVP_MD *md = _hashlib_hmac_get_md(self); - if (md == NULL) { - return NULL; - } - PyObject *digest_name = get_openssl_evp_md_name(md); + digest_name = md == NULL ? NULL : get_hashlib_utf8name_by_evp_md(md); if (digest_name == NULL) { + assert(PyErr_Occurred()); return NULL; } - PyObject *repr = PyUnicode_FromFormat( - "<%U HMAC object @ %p>", digest_name, self - ); - Py_DECREF(digest_name); - return repr; + return PyUnicode_FromFormat("<%s HMAC object @ %p>", digest_name, self); } /*[clinic input] @@ -1900,13 +1944,12 @@ _hashlib_hmac_get_name(PyObject *op, void *Py_UNUSED(closure)) if (md == NULL) { return NULL; } - PyObject *digest_name = get_openssl_evp_md_name(md); + const char *digest_name = get_hashlib_utf8name_by_evp_md(md); if (digest_name == NULL) { + assert(PyErr_Occurred()); return NULL; } - PyObject *name = PyUnicode_FromFormat("hmac-%U", digest_name); - Py_DECREF(digest_name); - return name; + return PyUnicode_FromFormat("hmac-%s", digest_name); } static PyMethodDef HMAC_methods[] = { @@ -1982,7 +2025,9 @@ _openssl_hash_name_mapper(const EVP_MD *md, const char *from, return; } - py_name = get_openssl_evp_md_name(md); + const char *name = get_hashlib_utf8name_by_evp_md(md); + assert(name != NULL || PyErr_Occurred()); + py_name = name == NULL ? NULL : PyUnicode_FromString(name); if (py_name == NULL) { state->error = 1; } else { From 380f5a0c33af4c4b10802d012f07b4e15bfcef75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:01:42 +0200 Subject: [PATCH 14/17] Update Modules/_hashopenssl.c --- Modules/_hashopenssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index b553a681f5820a..f63e52ccc6d5f9 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -396,7 +396,7 @@ get_hashentry_by_nid(int nid) } /* - * Convert the NID to a string via OBJ_nid2_*() functions. + * Convert the NID to a string via OBJ_nid2*() functions. * * If 'nid' cannot be resolved, set an exception and return NULL. */ From 7036df8aadb53908f012947565ee04b10feab067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:04:21 +0200 Subject: [PATCH 15/17] remove TODO --- Modules/_hashopenssl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index f63e52ccc6d5f9..8f4b900bdedab5 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -506,7 +506,6 @@ get_openssl_evp_md_by_utf8name(PyObject *module, const char *name, } } if (digest == NULL) { - // NOTE(picnixz): report hash type value instead of name raise_ssl_error(state->unsupported_digestmod_error, "unsupported hash type %s", name); return NULL; From a1066fe118caedd68f9806ca6321aadcce2f1513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 14:21:39 +0200 Subject: [PATCH 16/17] pull main --- Modules/_hashopenssl.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c index 7a5f203fced742..1deef507a6575d 100644 --- a/Modules/_hashopenssl.c +++ b/Modules/_hashopenssl.c @@ -472,16 +472,19 @@ get_asn1_utf8name_by_nid(int nid) // In OpenSSL 3.0 and later, OBJ_nid*() are thread-safe and may raise. assert(ERR_peek_last_error() != 0); if (ERR_GET_REASON(ERR_peek_last_error()) != OBJ_R_UNKNOWN_NID) { - notify_ssl_error_occurred(); - return NULL; + goto error; } // fallback to short name and unconditionally propagate errors name = OBJ_nid2sn(nid); if (name == NULL) { - raise_ssl_error(PyExc_ValueError, "cannot resolve NID %d", nid); + goto error; } } return name; + +error: + raise_ssl_error_f(PyExc_ValueError, "cannot resolve NID %d", nid); + return NULL; } /* From 35e6d1ee4badb45a43ce1a0338f21dd987278a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 8 Jun 2025 22:10:54 +0200 Subject: [PATCH 17/17] Update Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst --- .../Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst index 4ca59812dbe9dd..e1c11e46735f0b 100644 --- a/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst +++ b/Misc/NEWS.d/next/Library/2025-06-08-11-11-07.gh-issue-135234.wJCdh0.rst @@ -1,2 +1,3 @@ :mod:`hashlib`: improve exception messages when an OpenSSL function failed. -Patch by Bénédikt Tran. +When memory allocation fails on OpenSSL's side, a :exc:`MemoryError` is +raised instead of a :exc:`ValueError`. Patch by Bénédikt Tran. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy