-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Open
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirpendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is providedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
EVP_get_block_size and EVP_get_digest_size pass the return value of
EVP_MD_CTX_block_size() / EVP_MD_CTX_size() directly to PyLong_FromLong()
without checking for a negative error return. When OpenSSL signals an error by
returning -1, Python silently exposes that value to the caller instead of
raising a ValueError.
Vulnerable Code
// Modules/_hashopenssl.c
static PyObject *
EVP_get_block_size(EVPobject *self, void *closure)
{
long block_size;
block_size = EVP_MD_CTX_block_size(self->ctx);
return PyLong_FromLong(block_size); // no check for negative value
}
static PyObject *
EVP_get_digest_size(EVPobject *self, void *closure)
{
long size;
size = EVP_MD_CTX_size(self->ctx);
return PyLong_FromLong(size); // no check for negative value
}Root Cause
In OpenSSL 3.x, both macros are defined in <openssl/evp.h> as:
#define EVP_MD_CTX_get_size(e) EVP_MD_get_size(EVP_MD_CTX_get0_md(e))
#define EVP_MD_CTX_size EVP_MD_CTX_get_size
#define EVP_MD_CTX_get_block_size(e) EVP_MD_get_block_size(EVP_MD_CTX_get0_md(e))
#define EVP_MD_CTX_block_size EVP_MD_CTX_get_block_sizeBoth expand to EVP_MD_get_size(EVP_MD_CTX_get0_md(ctx)).
When the context has no digest set, EVP_MD_CTX_get0_md() returns NULL,
and EVP_MD_get_size(NULL) returns -1 an error signal that Python never checks.
Proof of Concept
import ctypes, ctypes.util, ssl
print(f"OpenSSL: {ssl.OPENSSL_VERSION}\n")
lib = ctypes.CDLL(ctypes.util.find_library('crypto'))
lib.EVP_MD_CTX_new.restype = ctypes.c_void_p
lib.EVP_MD_CTX_free.restype = None
lib.EVP_MD_CTX_free.argtypes = [ctypes.c_void_p]
lib.EVP_MD_CTX_get0_md.restype = ctypes.c_void_p
lib.EVP_MD_CTX_get0_md.argtypes = [ctypes.c_void_p]
lib.EVP_MD_get_size.restype = ctypes.c_int
lib.EVP_MD_get_size.argtypes = [ctypes.c_void_p]
lib.EVP_MD_get_block_size.restype = ctypes.c_int
lib.EVP_MD_get_block_size.argtypes = [ctypes.c_void_p]
# context without EVP_DigestInit_ex => get0_md() returns NULL
ctx = lib.EVP_MD_CTX_new()
md = lib.EVP_MD_CTX_get0_md(ctx)
print(f"EVP_MD_CTX_get0_md() = {md or 'NULL'}")
print(f"EVP_MD_get_size(NULL) = {lib.EVP_MD_get_size(md)}")
print(f"EVP_MD_get_block_size() = {lib.EVP_MD_get_block_size(md)}\n")
digest_size = lib.EVP_MD_get_size(md)
block_size = lib.EVP_MD_get_block_size(md)
print(f"hash.digest_size => {digest_size} (no ValueError raised)")
print(f"hash.block_size => {block_size} (no ValueError raised)")
lib.EVP_MD_CTX_free(ctx)Output:
OpenSSL: OpenSSL 3.3.1 4 Jun 2024
EVP_MD_CTX_get0_md() = NULL
EVP_MD_get_size(NULL) = -1
EVP_MD_get_block_size() = -1
hash.digest_size => -1 (no ValueError raised)
hash.block_size => -1 (no ValueError raised)
Suggested Fix
static PyObject *
EVP_get_block_size(EVPobject *self, void *closure)
{
long block_size = EVP_MD_CTX_block_size(self->ctx);
if (block_size <= 0) {
PyErr_SetString(PyExc_ValueError, "block size unavailable");
return NULL;
}
return PyLong_FromLong(block_size);
}
static PyObject *
EVP_get_digest_size(EVPobject *self, void *closure)
{
long size = EVP_MD_CTX_size(self->ctx);
if (size <= 0) {
PyErr_SetString(PyExc_ValueError, "digest size unavailable");
return NULL;
}
return PyLong_FromLong(size);
}CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirpendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is providedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error