From da3968c7b0ccf47355fbec333004c58fcc18a85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:57:49 +0100 Subject: [PATCH 01/38] introduce semantic types for SSL packed errors --- Modules/_ssl.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 74cf99957389e2..c61d299793b762 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -74,6 +74,7 @@ #endif +typedef unsigned long py_ssl_errcode; /* packed error (lib, func, reason) */ struct py_ssl_error_code { const char *mnemonic; @@ -192,7 +193,7 @@ extern const SSL_METHOD *TLSv1_2_method(void); #endif -enum py_ssl_error { +typedef enum py_ssl_error { /* these mirror ssl.h */ PY_SSL_ERROR_NONE, PY_SSL_ERROR_SSL, @@ -206,7 +207,7 @@ enum py_ssl_error { PY_SSL_ERROR_EOF, /* special case of SSL_ERROR_SYSCALL */ PY_SSL_ERROR_NO_SOCKET, /* socket has been GC'd */ PY_SSL_ERROR_INVALID_ERROR_CODE -}; +} py_ssl_error; enum py_ssl_server_or_client { PY_SSL_CLIENT, @@ -468,7 +469,7 @@ static PyType_Spec sslerror_type_spec = { static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *type, int ssl_errno, - const char *errstr, int lineno, unsigned long errcode) + const char *errstr, int lineno, py_ssl_errcode errcode) { PyObject *err_value = NULL, *reason_obj = NULL, *lib_obj = NULL; PyObject *verify_obj = NULL, *verify_code_obj = NULL; @@ -605,8 +606,8 @@ PySSL_SetError(PySSLSocket *sslsock, const char *filename, int lineno) PyObject *type; char *errstr = NULL; _PySSLError err; - enum py_ssl_error p = PY_SSL_ERROR_NONE; - unsigned long e = 0; + py_ssl_error p = PY_SSL_ERROR_NONE; + py_ssl_errcode e = 0; assert(sslsock != NULL); @@ -5687,7 +5688,7 @@ PySSL_RAND(PyObject *module, int len, int pseudo) { int ok; PyObject *bytes; - unsigned long err; + py_ssl_errcode err; const char *errstr; PyObject *v; From 704e6e37302571c89ab1f2d199447a85097999f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:37:01 +0100 Subject: [PATCH 02/38] correct error deduction in `fill_and_set_sslerror` and `_setSSLError` --- Modules/_ssl.c | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index c61d299793b762..b33b550e99ec38 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -314,10 +314,10 @@ typedef struct { } PySSLContext; typedef struct { - int ssl; /* last seen error from SSL */ - int c; /* last seen error from libc */ + int ssl; /* last seen error from TLS/SSL (see SSL_get_error(3)) */ + int c; /* last seen error from libc */ #ifdef MS_WINDOWS - int ws; /* last seen error from winsock */ + int ws; /* last seen error from winsock */ #endif } _PySSLError; @@ -468,8 +468,11 @@ static PyType_Spec sslerror_type_spec = { static void fill_and_set_sslerror(_sslmodulestate *state, - PySSLSocket *sslsock, PyObject *type, int ssl_errno, - const char *errstr, int lineno, py_ssl_errcode errcode) + PySSLSocket *sslsock, PyObject *exc_type, + int ssl_errno /* passed to exc.__init__() */, + py_ssl_errcode errcode /* for a default message */, + const char *errstr /* may be NULL */, + const char *Py_UNUSED(filename), int lineno) { PyObject *err_value = NULL, *reason_obj = NULL, *lib_obj = NULL; PyObject *verify_obj = NULL, *verify_code_obj = NULL; @@ -503,7 +506,7 @@ fill_and_set_sslerror(_sslmodulestate *state, errstr = "unknown error"; /* verify code for cert validation error */ - if ((sslsock != NULL) && (type == state->PySSLCertVerificationErrorObject)) { + if ((sslsock != NULL) && (exc_type == state->PySSLCertVerificationErrorObject)) { const char *verify_str = NULL; long verify_code; @@ -555,11 +558,11 @@ fill_and_set_sslerror(_sslmodulestate *state, if (msg == NULL) goto fail; - init_value = Py_BuildValue("iN", ERR_GET_REASON(ssl_errno), msg); + init_value = Py_BuildValue("iN", ssl_errno, msg); if (init_value == NULL) goto fail; - err_value = PyObject_CallObject(type, init_value); + err_value = PyObject_CallObject(exc_type, init_value); Py_DECREF(init_value); if (err_value == NULL) goto fail; @@ -574,7 +577,7 @@ fill_and_set_sslerror(_sslmodulestate *state, if (PyObject_SetAttr(err_value, state->str_library, lib_obj)) goto fail; - if ((sslsock != NULL) && (type == state->PySSLCertVerificationErrorObject)) { + if ((sslsock != NULL) && (exc_type == state->PySSLCertVerificationErrorObject)) { /* Only set verify code / message for SSLCertVerificationError */ if (PyObject_SetAttr(err_value, state->str_verify_code, verify_code_obj)) @@ -583,7 +586,7 @@ fill_and_set_sslerror(_sslmodulestate *state, goto fail; } - PyErr_SetObject(type, err_value); + PyErr_SetObject(exc_type, err_value); fail: Py_XDECREF(err_value); Py_XDECREF(verify_code_obj); @@ -710,20 +713,32 @@ PySSL_SetError(PySSLSocket *sslsock, const char *filename, int lineno) errstr = "Invalid error code"; } } - fill_and_set_sslerror(state, sslsock, type, p, errstr, lineno, e); + fill_and_set_sslerror(state, sslsock, type, p, e, errstr, filename, lineno); ERR_clear_error(); PySSL_ChainExceptions(sslsock); return NULL; } static PyObject * -_setSSLError (_sslmodulestate *state, const char *errstr, int errcode, const char *filename, int lineno) +_setSSLError(_sslmodulestate *state, + const char *errstr, int ssl_errno, + const char *filename, int lineno) { - if (errstr == NULL) + py_ssl_errcode errcode; + if (errstr == NULL) { errcode = ERR_peek_last_error(); - else - errcode = 0; - fill_and_set_sslerror(state, NULL, state->PySSLErrorObject, errcode, errstr, lineno, errcode); + if (ssl_errno == 0) { // Use the reason as the underlying errno. + ssl_errno = ERR_GET_REASON(errcode); + } + } + else { + errcode = 0; // to avoid using LIB and REASON fields + if (ssl_errno == 0) { // generic error happened with a message + ssl_errno = PY_SSL_ERROR_SSL; + } + } + fill_and_set_sslerror(state, NULL, state->PySSLErrorObject, ssl_errno, + errcode, errstr, filename, lineno); ERR_clear_error(); return NULL; } From 864339aadea3928a9fb884352f696ef55e9ce4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:24:29 +0100 Subject: [PATCH 03/38] pre-initialize default exception attributes --- Modules/_ssl.c | 73 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index b33b550e99ec38..3815c4561dbfdd 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6229,18 +6229,70 @@ PyDoc_STRVAR(module_doc, "Implementation module for SSL socket operations. See the socket module\n\ for documentation."); +static PyObject * +sslmodule_get_exception_dict(_sslmodulestate *state, bool verify_field) +{ + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + if (PyDict_SetItem(dict, state->str_library, Py_None) < 0) { + goto fail; + } + if (PyDict_SetItem(dict, state->str_reason, Py_None) < 0) { + goto fail; + } + if (verify_field) { + if (PyDict_SetItem(dict, state->str_verify_message, Py_None) < 0) { + goto fail; + } + } + return dict; +fail: + Py_DECREF(dict); + return NULL; +} + +static int +sslmodule_add_exception(PyObject *module, + _sslmodulestate *state, PyObject **state_attribute, + const char *module_attribute_name, + const char *name, const char *doc, PyObject *bases, + bool verify_field) +{ + PyObject *dict = sslmodule_get_exception_dict(state, verify_field); + if (dict == NULL) { + return -1; + } + *state_attribute = PyErr_NewExceptionWithDoc(name, doc, bases, dict); + Py_DECREF(dict); + if (*state_attribute == NULL) { + return -1; + } + if (PyModule_AddObjectRef(module, module_attribute_name, *state_attribute) < 0) { + // clean-up will be done when + return -1; + } + return 0; +} + static int sslmodule_init_exceptions(PyObject *module) { _sslmodulestate *state = get_ssl_state(module); PyObject *bases = NULL; -#define add_exception(exc, name, doc, base) \ -do { \ - (exc) = PyErr_NewExceptionWithDoc("ssl." name, (doc), (base), NULL); \ - if ((state) == NULL) goto error; \ - if (PyModule_AddObjectRef(module, name, exc) < 0) goto error; \ -} while(0) +#define add_exception_impl(ATTR, NAME, DOC, BASES, VERIFY_FIELD) \ + do { \ + int rc = sslmodule_add_exception( \ + module, state, &(ATTR), (NAME), \ + "ssl." NAME, (DOC), (BASES), \ + (VERIFY_FIELD) \ + ); \ + if (rc < 0) { \ + goto error; \ + } \ + } while(0) state->PySSLErrorObject = PyType_FromSpecWithBases( &sslerror_type_spec, PyExc_OSError); @@ -6256,14 +6308,16 @@ do { \ if (bases == NULL) { goto error; } - add_exception( + add_exception_impl( state->PySSLCertVerificationErrorObject, "SSLCertVerificationError", SSLCertVerificationError_doc, - bases + bases, + true ); Py_CLEAR(bases); +#define add_exception(e, n, d, b) add_exception_impl(e, n, d, b, 0) add_exception( state->PySSLZeroReturnErrorObject, "SSLZeroReturnError", @@ -6299,6 +6353,7 @@ do { \ state->PySSLErrorObject ); #undef add_exception +#undef add_exception_impl return 0; error: @@ -6770,12 +6825,12 @@ sslmodule_init_lock(PyObject *module) static PyModuleDef_Slot sslmodule_slots[] = { {Py_mod_exec, sslmodule_init_types}, + {Py_mod_exec, sslmodule_init_strings}, {Py_mod_exec, sslmodule_init_exceptions}, {Py_mod_exec, sslmodule_init_socketapi}, {Py_mod_exec, sslmodule_init_errorcodes}, {Py_mod_exec, sslmodule_init_constants}, {Py_mod_exec, sslmodule_init_versioninfo}, - {Py_mod_exec, sslmodule_init_strings}, {Py_mod_exec, sslmodule_init_lock}, {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {Py_mod_gil, Py_MOD_GIL_NOT_USED}, From 87a501dabd479850f65902f3c56fc73ce24d541e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:27:06 +0100 Subject: [PATCH 04/38] extract logic for fetching library and reason --- Modules/_ssl.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 3815c4561dbfdd..e777b2685d7084 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -466,6 +466,46 @@ static PyType_Spec sslerror_type_spec = { .slots = sslerror_type_slots }; +/* + * Get the library and reason strings from a packed error code. + * + * This stores NULL or new references to Unicode objects in 'lib' and 'reason', + * and thus the caller is responsible for calling Py_XDECREF() on them. + * + * This returns 0 if both 'lib' and 'reason' were successfully set, + * and -1 otherwise. + */ +static int +ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, + PyObject **lib, PyObject **reason) +{ + int rc; + PyObject *key = NULL; + int errlib = ERR_GET_LIB(errcode); + + key = PyLong_FromLong(errlib); + if (key == NULL) { + return -1; + } + + rc = PyDict_GetItemRef(state->lib_codes_to_names, key, lib); + Py_DECREF(key); + if (rc < 0) { + return -1; + } + + key = Py_BuildValue("ii", errlib, ERR_GET_REASON(errcode)); + if (key == NULL) { + return -1; + } + rc = PyDict_GetItemRef(state->err_codes_to_names, key, reason); + Py_DECREF(key); + if (rc < 0) { + return -1; + } + + return 0; +} static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *exc_type, From 7cb16c0b30a73f0c5191530bf5c74f7388087fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:30:56 +0100 Subject: [PATCH 05/38] extract logic for formatting error message --- Modules/_ssl.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index e777b2685d7084..297cf0a6434f7d 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -506,6 +506,50 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, return 0; } + +/* + * Construct a Unicode object containing the formatted SSL error message. + * + * The caller is responsible to pass ASCII-encoded objects. + */ +static PyObject * +format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, + const char *errstr, + const char *Py_UNUSED(filename), int lineno) +{ + assert(errstr != NULL); +#define CHECK_OBJECT(x) \ + do { \ + assert(x == NULL || PyUnicode_CheckExact(x)); \ + assert(x == NULL || PyUnicode_IS_ASCII(x)); \ + } while (0) + CHECK_OBJECT(lib); + CHECK_OBJECT(reason); + CHECK_OBJECT(verify); +#undef CHECK_OBJECT +#define OPTIONAL_UTF8(x) ((x) == NULL ? PyUnicode_AsUTF8((x)) : NULL) + const char *libstr = OPTIONAL_UTF8(lib); + const char *reastr = OPTIONAL_UTF8(reason); + const char *verstr = OPTIONAL_UTF8(verify); +#undef OPTIONAL_UTF8 + if (lib && reason && verify) { + // - [LIB: REASON] ERROR: VERIFY (FILENAME:LINENO) + return PyUnicode_FromFormat("[%s: %s] %s: %s (" __FILE__ ":%d)", + libstr, reastr, errstr, verstr, lineno); + } + if (lib && reason) { + // - [LIB: REASON] ERROR (FILENAME:LINENO) + return PyUnicode_FromFormat("[%s: %s] %s (" __FILE__ ":%d)", + libstr, reastr, errstr, lineno); + } + if (lib) { + /* [LIB] ERROR (FILENAME:LINENO) */ + return PyUnicode_FromFormat("[%s] %s (" __FILE__ ":%d)", + libstr, errstr, lineno); + } + /* ERROR (FILENAME:LINENO) */ + return PyUnicode_FromFormat("%s (" __FILE__ ":%d)", errstr, lineno); +} static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *exc_type, From 4bd14a09fd82e095374200e57fad231167a001f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:33:27 +0100 Subject: [PATCH 06/38] extract logic for building simple error message --- Modules/_ssl.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 297cf0a6434f7d..d9b4a82b65011e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -550,6 +550,67 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, /* ERROR (FILENAME:LINENO) */ return PyUnicode_FromFormat("%s (" __FILE__ ":%d)", errstr, lineno); } + +/* + * Construct an SSL error object of given type. + * + * Parameters + * + * exc_type The SSL exception type. + * ssl_errno The SSL error number to pass to the exception constructor. + * lib The ASCII-encoded library obtained from a packed error code. + * reason The ASCII-encoded reason obtained from a packed error code. + * errstr The error message to use. + * + * A non-NULL library or reason is stored in the final exception object. + */ +static PyObject * +build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno, + PyObject *lib, PyObject *reason, const char *errstr, + const char *filename, int lineno) +{ + /* build message */ + PyObject *message = format_ssl_error_message(lib, reason, NULL, errstr, + filename, lineno); + if (message == NULL) { + return NULL; + } + /* + * The SSL error codes that Python exposes consist of some libssl + * error codes as well as some custom ones. The reason is encoded + * in the lowest bits of the error code and thus is compatible with + * the ERR_GET_REASON() macro. + */ + assert(ssl_errno == ERR_GET_REASON(ssl_errno)); + PyObject *args = Py_BuildValue("iN", ssl_errno, message /* stolen */); + if (args == NULL) { + return NULL; + } + PyObject *exc = PyObject_CallObject(exc_type, args); + Py_DECREF(args); + if (exc == NULL) { + return NULL; + } + /* build attributes if desired */ + if (lib == NULL && reason == NULL) { + return exc; + } + PyObject *exc_dict = PyObject_GenericGetDict(exc, NULL); // borrowed + if (exc_dict == NULL) { + goto fail; + } + if (lib && PyDict_SetItem(exc_dict, state->str_library, lib) < 0) { + goto fail; + } + if (reason && PyDict_SetItem(exc_dict, state->str_library, reason) < 0) { + goto fail; + } + return exc; +fail: + Py_XDECREF(exc); + return NULL; +} + static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *exc_type, From c29f0f18725eb4b5b80287ddf22b926def0beb89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:39:15 +0100 Subject: [PATCH 07/38] extract logic for building SSL verification exceptions --- Modules/_ssl.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index d9b4a82b65011e..1eb3904f71e1e6 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -611,6 +611,90 @@ build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno return NULL; } +/* + * Same as build_ssl_simple_error() but for SSL verification errors. + */ +static PyObject * +build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_errno, + PyObject *lib, PyObject *reason, const char *errstr, + const char *filename, int lineno) +{ + assert(sslsock != NULL); + PyObject *exc = NULL, *verify = NULL, *verify_code = NULL; + /* verify code for cert validation error */ + long verify_code_value = SSL_get_verify_result(sslsock->ssl); + + const char *what = + verify_code_value == X509_V_ERR_HOSTNAME_MISMATCH + ? "Hostname" + : verify_code_value == X509_V_ERR_IP_ADDRESS_MISMATCH + ? "IP address" + : NULL; + + if (what == NULL) { + const char *s = X509_verify_cert_error_string(verify_code_value); + verify = s == NULL ? Py_None : PyUnicode_FromString(s); + } + else { + // The server's hostname is known to be an ASCII string. + assert(PyUnicode_IS_ASCII(sslsock->server_hostname)); + const char *hostname = PyUnicode_AsUTF8(sslsock->server_hostname); + verify = PyUnicode_FromFormat( + "%s mismatch, certificate is not valid for '%s'.", + what, hostname + ); + } + if (verify == NULL || verify_code == NULL) { + goto fail; + } + verify_code = PyLong_FromLong(verify_code_value); + if (verify_code == NULL) { + goto fail; + } + /* build message */ + PyObject *message = format_ssl_error_message(lib, reason, verify, + errstr, filename, lineno); + if (message == NULL) { + goto fail; + } + /* see build_ssl_simple_error() for the assertion's rationale */ + assert(ssl_errno == ERR_GET_REASON(ssl_errno)); + PyObject *args = Py_BuildValue("iN", ssl_errno, message /* stolen */); + if (args == NULL) { + goto fail; + } + exc = PyObject_CallObject(state->PySSLCertVerificationErrorObject, args); + Py_DECREF(args); + if (exc == NULL) { + goto fail; + } + /* build attributes */ + PyObject *exc_dict = PyObject_GenericGetDict(exc, NULL); // borrowed + assert(exc_dict != NULL); + if (exc_dict == NULL) { + goto fail; + } + #define SET_ATTR(a, x) \ + do { \ + if ((x) && PyDict_SetItem(exc_dict, (a), (x)) < 0) { \ + goto fail; \ + } \ + } while (0) + SET_ATTR(state->str_library, lib); + SET_ATTR(state->str_reason, reason); + SET_ATTR(state->str_verify_message, verify); + SET_ATTR(state->str_verify_code, verify_code); + #undef SET_ATTR + Py_DECREF(verify_code); + Py_DECREF(verify); + return exc; +fail: + Py_XDECREF(verify_code); + Py_XDECREF(verify); + Py_XDECREF(exc); + return NULL; +} + static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *exc_type, From d8de9927ce3076c1fccd7fc145dec22f2323dce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:41:14 +0100 Subject: [PATCH 08/38] upgrade logic for building SSL exceptions --- Modules/_ssl.c | 142 ++++++++++--------------------------------------- 1 file changed, 27 insertions(+), 115 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 1eb3904f71e1e6..ce3d4a31451228 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -701,125 +701,37 @@ fill_and_set_sslerror(_sslmodulestate *state, int ssl_errno /* passed to exc.__init__() */, py_ssl_errcode errcode /* for a default message */, const char *errstr /* may be NULL */, - const char *Py_UNUSED(filename), int lineno) -{ - PyObject *err_value = NULL, *reason_obj = NULL, *lib_obj = NULL; - PyObject *verify_obj = NULL, *verify_code_obj = NULL; - PyObject *init_value, *msg, *key; - - if (errcode != 0) { - int lib, reason; - - lib = ERR_GET_LIB(errcode); - reason = ERR_GET_REASON(errcode); - key = Py_BuildValue("ii", lib, reason); - if (key == NULL) - goto fail; - reason_obj = PyDict_GetItemWithError(state->err_codes_to_names, key); - Py_DECREF(key); - if (reason_obj == NULL && PyErr_Occurred()) { - goto fail; + const char *filename, int lineno) +{ + PyObject *lib = NULL, *reason = NULL; + if (errcode) { + if (ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason) < 0) { + Py_XDECREF(reason); + Py_XDECREF(lib); + return; } - key = PyLong_FromLong(lib); - if (key == NULL) - goto fail; - lib_obj = PyDict_GetItemWithError(state->lib_codes_to_names, key); - Py_DECREF(key); - if (lib_obj == NULL && PyErr_Occurred()) { - goto fail; - } - if (errstr == NULL) - errstr = ERR_reason_error_string(errcode); } - if (errstr == NULL) - errstr = "unknown error"; - - /* verify code for cert validation error */ - if ((sslsock != NULL) && (exc_type == state->PySSLCertVerificationErrorObject)) { - const char *verify_str = NULL; - long verify_code; - - verify_code = SSL_get_verify_result(sslsock->ssl); - verify_code_obj = PyLong_FromLong(verify_code); - if (verify_code_obj == NULL) { - goto fail; - } - - switch (verify_code) { - case X509_V_ERR_HOSTNAME_MISMATCH: - verify_obj = PyUnicode_FromFormat( - "Hostname mismatch, certificate is not valid for '%S'.", - sslsock->server_hostname - ); - break; - case X509_V_ERR_IP_ADDRESS_MISMATCH: - verify_obj = PyUnicode_FromFormat( - "IP address mismatch, certificate is not valid for '%S'.", - sslsock->server_hostname - ); - break; - default: - verify_str = X509_verify_cert_error_string(verify_code); - if (verify_str != NULL) { - verify_obj = PyUnicode_FromString(verify_str); - } else { - verify_obj = Py_NewRef(Py_None); - } - break; - } - if (verify_obj == NULL) { - goto fail; - } + if (errstr == NULL) { + errstr = errcode + ? ERR_reason_error_string(errcode) + : "unknown error"; } - - if (verify_obj && reason_obj && lib_obj) - msg = PyUnicode_FromFormat("[%S: %S] %s: %S (_ssl.c:%d)", - lib_obj, reason_obj, errstr, verify_obj, - lineno); - else if (reason_obj && lib_obj) - msg = PyUnicode_FromFormat("[%S: %S] %s (_ssl.c:%d)", - lib_obj, reason_obj, errstr, lineno); - else if (lib_obj) - msg = PyUnicode_FromFormat("[%S] %s (_ssl.c:%d)", - lib_obj, errstr, lineno); - else - msg = PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno); - if (msg == NULL) - goto fail; - - init_value = Py_BuildValue("iN", ssl_errno, msg); - if (init_value == NULL) - goto fail; - - err_value = PyObject_CallObject(exc_type, init_value); - Py_DECREF(init_value); - if (err_value == NULL) - goto fail; - - if (reason_obj == NULL) - reason_obj = Py_None; - if (PyObject_SetAttr(err_value, state->str_reason, reason_obj)) - goto fail; - - if (lib_obj == NULL) - lib_obj = Py_None; - if (PyObject_SetAttr(err_value, state->str_library, lib_obj)) - goto fail; - - if ((sslsock != NULL) && (exc_type == state->PySSLCertVerificationErrorObject)) { - /* Only set verify code / message for SSLCertVerificationError */ - if (PyObject_SetAttr(err_value, state->str_verify_code, - verify_code_obj)) - goto fail; - if (PyObject_SetAttr(err_value, state->str_verify_message, verify_obj)) - goto fail; + PyObject *exc; + if (sslsock != NULL && exc_type == state->PySSLCertVerificationErrorObject) { + exc = build_ssl_verify_error(state, sslsock, ssl_errno, + lib, reason, errstr, + filename, lineno); + } + else { + exc = build_ssl_simple_error(state, exc_type, ssl_errno, + lib, reason, errstr, + filename, lineno); + } + Py_XDECREF(reason); + Py_XDECREF(lib); + if (exc != NULL) { + PyErr_SetObject(exc_type, exc); } - - PyErr_SetObject(exc_type, err_value); -fail: - Py_XDECREF(err_value); - Py_XDECREF(verify_code_obj); - Py_XDECREF(verify_obj); } static int From ba1bf20e0a570cd7ab9cfb3317f0f7941557c2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 31 Dec 2024 14:09:01 +0100 Subject: [PATCH 09/38] fix various issues --- Modules/_ssl.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ce3d4a31451228..7189cd6f027928 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -472,8 +472,8 @@ static PyType_Spec sslerror_type_spec = { * This stores NULL or new references to Unicode objects in 'lib' and 'reason', * and thus the caller is responsible for calling Py_XDECREF() on them. * - * This returns 0 if both 'lib' and 'reason' were successfully set, - * and -1 otherwise. + * This returns 0 if both 'lib' and 'reason' were successfully set (possibly + * to NULL), and -1 otherwise. */ static int ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, @@ -487,7 +487,6 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, if (key == NULL) { return -1; } - rc = PyDict_GetItemRef(state->lib_codes_to_names, key, lib); Py_DECREF(key); if (rc < 0) { @@ -503,7 +502,6 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, if (rc < 0) { return -1; } - return 0; } @@ -527,7 +525,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, CHECK_OBJECT(reason); CHECK_OBJECT(verify); #undef CHECK_OBJECT -#define OPTIONAL_UTF8(x) ((x) == NULL ? PyUnicode_AsUTF8((x)) : NULL) +#define OPTIONAL_UTF8(x) ((x) == NULL ? NULL : PyUnicode_AsUTF8((x))) const char *libstr = OPTIONAL_UTF8(lib); const char *reastr = OPTIONAL_UTF8(reason); const char *verstr = OPTIONAL_UTF8(verify); @@ -595,18 +593,20 @@ build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno if (lib == NULL && reason == NULL) { return exc; } - PyObject *exc_dict = PyObject_GenericGetDict(exc, NULL); // borrowed + PyObject *exc_dict = PyObject_GenericGetDict(exc, NULL); if (exc_dict == NULL) { goto fail; } if (lib && PyDict_SetItem(exc_dict, state->str_library, lib) < 0) { goto fail; } - if (reason && PyDict_SetItem(exc_dict, state->str_library, reason) < 0) { + if (reason && PyDict_SetItem(exc_dict, state->str_reason, reason) < 0) { goto fail; } + Py_DECREF(exc_dict); return exc; fail: + Py_XDECREF(exc_dict); Py_XDECREF(exc); return NULL; } @@ -620,7 +620,7 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err const char *filename, int lineno) { assert(sslsock != NULL); - PyObject *exc = NULL, *verify = NULL, *verify_code = NULL; + PyObject *exc = NULL, *exc_dict = NULL, *verify = NULL, *verify_code = NULL; /* verify code for cert validation error */ long verify_code_value = SSL_get_verify_result(sslsock->ssl); @@ -644,7 +644,7 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err what, hostname ); } - if (verify == NULL || verify_code == NULL) { + if (verify == NULL) { goto fail; } verify_code = PyLong_FromLong(verify_code_value); @@ -669,12 +669,11 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err goto fail; } /* build attributes */ - PyObject *exc_dict = PyObject_GenericGetDict(exc, NULL); // borrowed - assert(exc_dict != NULL); + exc_dict = PyObject_GenericGetDict(exc, NULL); if (exc_dict == NULL) { goto fail; } - #define SET_ATTR(a, x) \ +#define SET_ATTR(a, x) \ do { \ if ((x) && PyDict_SetItem(exc_dict, (a), (x)) < 0) { \ goto fail; \ @@ -684,11 +683,13 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err SET_ATTR(state->str_reason, reason); SET_ATTR(state->str_verify_message, verify); SET_ATTR(state->str_verify_code, verify_code); - #undef SET_ATTR +#undef SET_ATTR + Py_DECREF(exc_dict); Py_DECREF(verify_code); Py_DECREF(verify); return exc; fail: + Py_XDECREF(exc_dict); Py_XDECREF(verify_code); Py_XDECREF(verify); Py_XDECREF(exc); @@ -729,9 +730,8 @@ fill_and_set_sslerror(_sslmodulestate *state, } Py_XDECREF(reason); Py_XDECREF(lib); - if (exc != NULL) { - PyErr_SetObject(exc_type, exc); - } + PyErr_SetObject(exc_type, exc); + Py_XDECREF(exc); } static int From 11ee17c344f55786978f02defcdfc9f88fbb1c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:18:28 +0100 Subject: [PATCH 10/38] use `PyOS_snprintf` instead of `PyUnicode_FromFormat` --- Modules/_ssl.c | 63 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 7189cd6f027928..f67cf43ee53848 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -516,6 +516,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, const char *Py_UNUSED(filename), int lineno) { assert(errstr != NULL); + #define CHECK_OBJECT(x) \ do { \ assert(x == NULL || PyUnicode_CheckExact(x)); \ @@ -525,28 +526,62 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, CHECK_OBJECT(reason); CHECK_OBJECT(verify); #undef CHECK_OBJECT + #define OPTIONAL_UTF8(x) ((x) == NULL ? NULL : PyUnicode_AsUTF8((x))) const char *libstr = OPTIONAL_UTF8(lib); const char *reastr = OPTIONAL_UTF8(reason); const char *verstr = OPTIONAL_UTF8(verify); #undef OPTIONAL_UTF8 + + const size_t errstr_len = strlen(errstr); + const size_t libstr_len = libstr == NULL ? 0 : strlen(libstr); + const size_t reastr_len = reastr == NULL ? 0 : strlen(reastr); + const size_t verstr_len = verstr == NULL ? 0 : strlen(verstr); + const size_t filename_len = 6; /* strlen("_ssl.c") == strlen(__FILE__) */ + /* upper bound on the number of characters taken by the line number */ + const size_t lineno_len = ceil(log10(abs(lineno) + 1)); + const size_t base_alloc = ( + libstr_len + reastr_len + verstr_len + + errstr_len + filename_len + lineno_len + ); + + int rc; + char *buf; + if (lib && reason && verify) { - // - [LIB: REASON] ERROR: VERIFY (FILENAME:LINENO) - return PyUnicode_FromFormat("[%s: %s] %s: %s (" __FILE__ ":%d)", - libstr, reastr, errstr, verstr, lineno); - } - if (lib && reason) { - // - [LIB: REASON] ERROR (FILENAME:LINENO) - return PyUnicode_FromFormat("[%s: %s] %s (" __FILE__ ":%d)", - libstr, reastr, errstr, lineno); - } - if (lib) { + /* [LIB: REASON] ERROR: VERIFY (FILENAME:LINENO) */ + const size_t alloc = base_alloc + (4 + 3 + 4); + buf = (char *)PyMem_RawMalloc(alloc); + rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (" __FILE__ ":%d)", + libstr, reastr, errstr, verstr, lineno); + } + else if (lib && reason) { + /* [LIB: REASON] ERROR (FILENAME:LINENO) */ + const size_t alloc = base_alloc + (3 + 2 + 4); + buf = (char *)PyMem_RawMalloc(alloc); + rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (" __FILE__ ":%d)", + libstr, reastr, errstr, lineno); + } + else if (lib) { /* [LIB] ERROR (FILENAME:LINENO) */ - return PyUnicode_FromFormat("[%s] %s (" __FILE__ ":%d)", - libstr, errstr, lineno); + const size_t alloc = base_alloc + (2 + 1 + 4); + buf = (char *)PyMem_RawMalloc(alloc); + rc = PyOS_snprintf(buf, alloc, "[%s] %s (" __FILE__ ":%d)", + libstr, errstr, lineno); + } + else { + /* ERROR (FILENAME:LINENO) */ + const size_t alloc = base_alloc + (1 + 1 + 2); + buf = (char *)PyMem_RawMalloc(alloc); + rc = PyOS_snprintf(buf, alloc, "%s (" __FILE__ ":%d)", + errstr, lineno); } - /* ERROR (FILENAME:LINENO) */ - return PyUnicode_FromFormat("%s (" __FILE__ ":%d)", errstr, lineno); + + PyObject *res = rc < 0 + ? PyUnicode_FromFormat("%s (" __FILE__ ":%d)", errstr, lineno) + : PyUnicode_FromString(buf) /* uses the ASCII fast path */; + PyMem_RawFree(buf); + return res; } /* From 45c7a1edc1a8b0afc1d1f2dc8feed3a0748e8555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 1 Jan 2025 13:57:42 +0100 Subject: [PATCH 11/38] fix Windows compilation --- Modules/_ssl.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index f67cf43ee53848..4f072f9b60795d 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -537,7 +537,12 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, const size_t libstr_len = libstr == NULL ? 0 : strlen(libstr); const size_t reastr_len = reastr == NULL ? 0 : strlen(reastr); const size_t verstr_len = verstr == NULL ? 0 : strlen(verstr); - const size_t filename_len = 6; /* strlen("_ssl.c") == strlen(__FILE__) */ + /* + * Sometimes, __FILE__ is an absolute path, so we hardcode "_ssl.c". + * In the future, we might want to use the "filename" parameter but + * for now (and for backward compatibility), we ignore it. + */ + const size_t filename_len = 6; /* strlen("_ssl.c") */ /* upper bound on the number of characters taken by the line number */ const size_t lineno_len = ceil(log10(abs(lineno) + 1)); const size_t base_alloc = ( @@ -549,36 +554,36 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, char *buf; if (lib && reason && verify) { - /* [LIB: REASON] ERROR: VERIFY (FILENAME:LINENO) */ + /* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (4 + 3 + 4); buf = (char *)PyMem_RawMalloc(alloc); - rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (" __FILE__ ":%d)", + rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (_ssl.c:%d)", libstr, reastr, errstr, verstr, lineno); } else if (lib && reason) { - /* [LIB: REASON] ERROR (FILENAME:LINENO) */ + /* [LIB: REASON] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (3 + 2 + 4); buf = (char *)PyMem_RawMalloc(alloc); - rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (" __FILE__ ":%d)", + rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (_ssl.c:%d)", libstr, reastr, errstr, lineno); } else if (lib) { - /* [LIB] ERROR (FILENAME:LINENO) */ + /* [LIB] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (2 + 1 + 4); buf = (char *)PyMem_RawMalloc(alloc); - rc = PyOS_snprintf(buf, alloc, "[%s] %s (" __FILE__ ":%d)", + rc = PyOS_snprintf(buf, alloc, "[%s] %s (_ssl.c:%d)", libstr, errstr, lineno); } else { - /* ERROR (FILENAME:LINENO) */ + /* ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (1 + 1 + 2); buf = (char *)PyMem_RawMalloc(alloc); - rc = PyOS_snprintf(buf, alloc, "%s (" __FILE__ ":%d)", + rc = PyOS_snprintf(buf, alloc, "%s (_ssl.c:%d)", errstr, lineno); } PyObject *res = rc < 0 - ? PyUnicode_FromFormat("%s (" __FILE__ ":%d)", errstr, lineno) + ? PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno) : PyUnicode_FromString(buf) /* uses the ASCII fast path */; PyMem_RawFree(buf); return res; From 4362dae668036602cafba4265c39a79034f310a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 1 Jan 2025 17:43:44 +0100 Subject: [PATCH 12/38] Apply suggestions from code review --- Modules/_ssl.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 4f072f9b60795d..eb346b5416f3c5 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -543,8 +543,11 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, * for now (and for backward compatibility), we ignore it. */ const size_t filename_len = 6; /* strlen("_ssl.c") */ - /* upper bound on the number of characters taken by the line number */ - const size_t lineno_len = ceil(log10(abs(lineno) + 1)); + // Crude upper bound on the number of characters taken by the line number. + // We expect -1 <= lineno < 1e8. More lines are unlikely to happen. + assert(lineno > -1); + assert(lineno < 1e8); + const size_t lineno_len = 8; const size_t base_alloc = ( libstr_len + reastr_len + verstr_len + errstr_len + filename_len + lineno_len @@ -556,28 +559,29 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, if (lib && reason && verify) { /* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (4 + 3 + 4); - buf = (char *)PyMem_RawMalloc(alloc); + /* PyMem_Malloc() is optimized for small buffers */ + buf = (char *)PyMem_Malloc(alloc); rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (_ssl.c:%d)", libstr, reastr, errstr, verstr, lineno); } else if (lib && reason) { /* [LIB: REASON] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (3 + 2 + 4); - buf = (char *)PyMem_RawMalloc(alloc); + buf = (char *)PyMem_Malloc(alloc); rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (_ssl.c:%d)", libstr, reastr, errstr, lineno); } else if (lib) { /* [LIB] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (2 + 1 + 4); - buf = (char *)PyMem_RawMalloc(alloc); + buf = (char *)PyMem_Malloc(alloc); rc = PyOS_snprintf(buf, alloc, "[%s] %s (_ssl.c:%d)", libstr, errstr, lineno); } else { /* ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (1 + 1 + 2); - buf = (char *)PyMem_RawMalloc(alloc); + buf = (char *)PyMem_Malloc(alloc); rc = PyOS_snprintf(buf, alloc, "%s (_ssl.c:%d)", errstr, lineno); } @@ -585,7 +589,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, PyObject *res = rc < 0 ? PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno) : PyUnicode_FromString(buf) /* uses the ASCII fast path */; - PyMem_RawFree(buf); + PyMem_Free(buf); return res; } From 31ce5f0e67645837fe7de519c0be2376536b1e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 1 Jan 2025 17:44:21 +0100 Subject: [PATCH 13/38] Update Modules/_ssl.c --- Modules/_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index eb346b5416f3c5..7684a566f030a0 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -545,7 +545,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, const size_t filename_len = 6; /* strlen("_ssl.c") */ // Crude upper bound on the number of characters taken by the line number. // We expect -1 <= lineno < 1e8. More lines are unlikely to happen. - assert(lineno > -1); + assert(lineno >= -1); assert(lineno < 1e8); const size_t lineno_len = 8; const size_t base_alloc = ( From 2fbcae29ec6f4a41e89cea382745bdc882aac592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:26:18 +0100 Subject: [PATCH 14/38] post-merge --- Modules/_ssl.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 7684a566f030a0..7ffac5147fe5c3 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -543,8 +543,10 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, * for now (and for backward compatibility), we ignore it. */ const size_t filename_len = 6; /* strlen("_ssl.c") */ - // Crude upper bound on the number of characters taken by the line number. - // We expect -1 <= lineno < 1e8. More lines are unlikely to happen. + /* + * Crude upper bound on the number of characters taken by the line number. + * We expect -1 <= lineno < 1e8. More lines are unlikely to happen. + */ assert(lineno >= -1); assert(lineno < 1e8); const size_t lineno_len = 8; @@ -560,33 +562,33 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, /* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (4 + 3 + 4); /* PyMem_Malloc() is optimized for small buffers */ - buf = (char *)PyMem_Malloc(alloc); + buf = PyMem_New(char, alloc); rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (_ssl.c:%d)", libstr, reastr, errstr, verstr, lineno); } else if (lib && reason) { /* [LIB: REASON] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (3 + 2 + 4); - buf = (char *)PyMem_Malloc(alloc); + buf = PyMem_New(char, alloc); rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (_ssl.c:%d)", libstr, reastr, errstr, lineno); } else if (lib) { /* [LIB] ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (2 + 1 + 4); - buf = (char *)PyMem_Malloc(alloc); + buf = PyMem_New(char, alloc); rc = PyOS_snprintf(buf, alloc, "[%s] %s (_ssl.c:%d)", libstr, errstr, lineno); } else { /* ERROR (_ssl.c:LINENO) */ const size_t alloc = base_alloc + (1 + 1 + 2); - buf = (char *)PyMem_Malloc(alloc); + buf = PyMem_New(char, alloc); rc = PyOS_snprintf(buf, alloc, "%s (_ssl.c:%d)", errstr, lineno); } - PyObject *res = rc < 0 + PyObject *res = rc < 0 /* fallback to slow path if snprintf() failed */ ? PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno) : PyUnicode_FromString(buf) /* uses the ASCII fast path */; PyMem_Free(buf); @@ -743,7 +745,7 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err static void fill_and_set_sslerror(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *exc_type, - int ssl_errno /* passed to exc.__init__() */, + int ssl_errno /* passed to exc_type.__init__() */, py_ssl_errcode errcode /* for a default message */, const char *errstr /* may be NULL */, const char *filename, int lineno) From fd7d3ecc7693481b659597f1e3bf4a536f36dffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:18:13 +0100 Subject: [PATCH 15/38] use `_Py_hashtable_t` for `err_codes_to_names` --- Modules/_ssl.c | 104 ++++++++++++++++++++++++++++++++----------------- Modules/_ssl.h | 4 +- 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 7ffac5147fe5c3..6745774b406e3c 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -74,12 +74,12 @@ #endif -typedef unsigned long py_ssl_errcode; /* packed error (lib, func, reason) */ +typedef unsigned long py_ssl_errcode; /* packed error (lib, func, reason) */ -struct py_ssl_error_code { +typedef struct py_ssl_error_code { const char *mnemonic; int library, reason; -}; +} py_ssl_error_code; struct py_ssl_library_code { const char *library; @@ -482,6 +482,7 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, int rc; PyObject *key = NULL; int errlib = ERR_GET_LIB(errcode); + int errrea = ERR_GET_REASON(errcode); key = PyLong_FromLong(errlib); if (key == NULL) { @@ -493,15 +494,14 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, return -1; } - key = Py_BuildValue("ii", errlib, ERR_GET_REASON(errcode)); - if (key == NULL) { - return -1; - } - rc = PyDict_GetItemRef(state->err_codes_to_names, key, reason); - Py_DECREF(key); - if (rc < 0) { + const void *key2 = (const void *)((uintptr_t)ERR_PACK(errlib, 0, errrea)); + const void *val2 = _Py_hashtable_get(state->err_codes_to_names, key2); + if (val2 == NULL) { + *reason = NULL; return -1; } + assert(PyUnicode_CheckExact(val2)); + *reason = Py_NewRef((PyObject *)val2); return 0; } @@ -6823,42 +6823,73 @@ sslmodule_init_constants(PyObject *m) return 0; } +/* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ +static Py_uhash_t +py_ht_errcode_to_name_hash(const void *key) { + return (Py_uhash_t)(py_ssl_errcode)((uintptr_t)key); +} + +static int +py_ht_errcode_to_name_comp(const void *a, const void *b) { + py_ssl_errcode a_code = (py_ssl_errcode)((uintptr_t)a); + py_ssl_errcode b_code = (py_ssl_errcode)((uintptr_t)b); + return a_code == b_code; +} + +static void +py_ht_errcode_to_name_free(void *value) { + assert(PyUnicode_CheckExact((PyObject *)value)); + Py_CLEAR(value); +} + +static _Py_hashtable_t * +py_ht_errcode_to_name_create(void) { + _Py_hashtable_t *table = _Py_hashtable_new_full( + py_ht_errcode_to_name_hash, + py_ht_errcode_to_name_comp, + NULL, + py_ht_errcode_to_name_free, + NULL + ); + if (table == NULL) { + PyErr_NoMemory(); + return NULL; + } + + for (const py_ssl_error_code *p = error_codes; p->mnemonic != NULL; p++) { + py_ssl_errcode code = ERR_PACK(p->library, 0, p->reason); + const void *key = (const void *)((uintptr_t)code); + PyObject *value = PyUnicode_FromString(p->mnemonic); + if (value == NULL) { + goto error; + } + if (_Py_hashtable_set(table, key, value) < 0) { + Py_DECREF(value); + goto error; + } + } + return table; +error: + _Py_hashtable_destroy(table); + return NULL; +} + static int sslmodule_init_errorcodes(PyObject *module) { _sslmodulestate *state = get_ssl_state(module); - struct py_ssl_error_code *errcode; struct py_ssl_library_code *libcode; /* Mappings for error codes */ - state->err_codes_to_names = PyDict_New(); - if (state->err_codes_to_names == NULL) + state->err_codes_to_names = py_ht_errcode_to_name_create(); + if (state->err_codes_to_names == NULL) { return -1; + } state->lib_codes_to_names = PyDict_New(); if (state->lib_codes_to_names == NULL) return -1; - errcode = error_codes; - while (errcode->mnemonic != NULL) { - PyObject *mnemo = PyUnicode_FromString(errcode->mnemonic); - if (mnemo == NULL) { - return -1; - } - PyObject *key = Py_BuildValue("ii", errcode->library, errcode->reason); - if (key == NULL) { - Py_DECREF(mnemo); - return -1; - } - int rc = PyDict_SetItem(state->err_codes_to_names, key, mnemo); - Py_DECREF(key); - Py_DECREF(mnemo); - if (rc < 0) { - return -1; - } - errcode++; - } - libcode = library_codes; while (libcode->library != NULL) { PyObject *mnemo, *key; @@ -7041,7 +7072,6 @@ sslmodule_traverse(PyObject *m, visitproc visit, void *arg) Py_VISIT(state->PySSLWantWriteErrorObject); Py_VISIT(state->PySSLSyscallErrorObject); Py_VISIT(state->PySSLEOFErrorObject); - Py_VISIT(state->err_codes_to_names); Py_VISIT(state->lib_codes_to_names); Py_VISIT(state->Sock_Type); @@ -7065,7 +7095,11 @@ sslmodule_clear(PyObject *m) Py_CLEAR(state->PySSLWantWriteErrorObject); Py_CLEAR(state->PySSLSyscallErrorObject); Py_CLEAR(state->PySSLEOFErrorObject); - Py_CLEAR(state->err_codes_to_names); + if (state->err_codes_to_names != NULL) { + _Py_hashtable_destroy(state->err_codes_to_names); + state->err_codes_to_names = NULL; + } + Py_CLEAR(state->lib_codes_to_names); Py_CLEAR(state->Sock_Type); Py_CLEAR(state->str_library); diff --git a/Modules/_ssl.h b/Modules/_ssl.h index 22d93ddcc6d6eb..28cc2ab7a93026 100644 --- a/Modules/_ssl.h +++ b/Modules/_ssl.h @@ -1,6 +1,8 @@ #ifndef Py_SSL_H #define Py_SSL_H +#include "pycore_hashtable.h" + /* OpenSSL header files */ #include "openssl/evp.h" #include "openssl/x509.h" @@ -24,7 +26,7 @@ typedef struct { PyObject *PySSLSyscallErrorObject; PyObject *PySSLEOFErrorObject; /* Error mappings */ - PyObject *err_codes_to_names; + _Py_hashtable_t *err_codes_to_names; PyObject *lib_codes_to_names; /* socket type from module CAPI */ PyTypeObject *Sock_Type; From 7772113fc8f8436ec903bc2b6a223bf9d57d032b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:26:54 +0100 Subject: [PATCH 16/38] use `_Py_hashtable_t` for `lib_codes_to_names` --- Modules/_ssl.c | 96 ++++++++++++++++++++++++++++++++------------------ Modules/_ssl.h | 2 +- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 6745774b406e3c..c8c06ad85e1f0e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -81,10 +81,10 @@ typedef struct py_ssl_error_code { int library, reason; } py_ssl_error_code; -struct py_ssl_library_code { +typedef struct py_ssl_library_code { const char *library; int code; -}; +} py_ssl_library_code; #if defined(MS_WINDOWS) && defined(Py_DEBUG) /* Debug builds on Windows rely on getting errno directly from OpenSSL. @@ -479,20 +479,17 @@ static int ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, PyObject **lib, PyObject **reason) { - int rc; - PyObject *key = NULL; int errlib = ERR_GET_LIB(errcode); int errrea = ERR_GET_REASON(errcode); - key = PyLong_FromLong(errlib); - if (key == NULL) { - return -1; - } - rc = PyDict_GetItemRef(state->lib_codes_to_names, key, lib); - Py_DECREF(key); - if (rc < 0) { + const void *key1 = (const void *)((uintptr_t)errlib); + const void *val1 = _Py_hashtable_get(state->lib_codes_to_names, key1); + if (val1 == NULL) { + *lib = NULL; return -1; } + assert(PyUnicode_CheckExact(val1)); + *lib = Py_NewRef((PyObject *)val1); const void *key2 = (const void *)((uintptr_t)ERR_PACK(errlib, 0, errrea)); const void *val2 = _Py_hashtable_get(state->err_codes_to_names, key2); @@ -6874,36 +6871,66 @@ py_ht_errcode_to_name_create(void) { return NULL; } +/* internal hashtable (libcode) => (libname [PyObject * (unicode)]) */ +static Py_uhash_t +py_ht_libcode_to_name_hash(const void *key) { + return (Py_uhash_t)((uintptr_t)key); +} + +static int +py_ht_libcode_to_name_comp(const void *a, const void *b) { + return (uintptr_t)a == (uintptr_t)b; +} + +static void +py_ht_libcode_to_name_free(void *value) { + assert(PyUnicode_CheckExact((PyObject *)value)); + Py_CLEAR(value); +} + +static _Py_hashtable_t * +py_ht_libcode_to_name_create(void) { + _Py_hashtable_t *table = _Py_hashtable_new_full( + py_ht_libcode_to_name_hash, + py_ht_libcode_to_name_comp, + NULL, + py_ht_libcode_to_name_free, + NULL + ); + if (table == NULL) { + PyErr_NoMemory(); + return NULL; + } + + for (const py_ssl_library_code *p = library_codes; p->library != NULL; p++) { + const void *key = (const void *)((uintptr_t)p->code); + PyObject *value = PyUnicode_FromString(p->library); + if (value == NULL) { + goto error; + } + if (_Py_hashtable_set(table, key, value) < 0) { + Py_DECREF(value); + goto error; + } + } + return table; +error: + _Py_hashtable_destroy(table); + return NULL; +} + static int sslmodule_init_errorcodes(PyObject *module) { _sslmodulestate *state = get_ssl_state(module); - - struct py_ssl_library_code *libcode; - - /* Mappings for error codes */ state->err_codes_to_names = py_ht_errcode_to_name_create(); if (state->err_codes_to_names == NULL) { return -1; } - state->lib_codes_to_names = PyDict_New(); - if (state->lib_codes_to_names == NULL) + state->lib_codes_to_names = py_ht_libcode_to_name_create(); + if (state->lib_codes_to_names == NULL) { return -1; - - libcode = library_codes; - while (libcode->library != NULL) { - PyObject *mnemo, *key; - key = PyLong_FromLong(libcode->code); - mnemo = PyUnicode_FromString(libcode->library); - if (key == NULL || mnemo == NULL) - return -1; - if (PyDict_SetItem(state->lib_codes_to_names, key, mnemo)) - return -1; - Py_DECREF(key); - Py_DECREF(mnemo); - libcode++; } - return 0; } @@ -7072,7 +7099,6 @@ sslmodule_traverse(PyObject *m, visitproc visit, void *arg) Py_VISIT(state->PySSLWantWriteErrorObject); Py_VISIT(state->PySSLSyscallErrorObject); Py_VISIT(state->PySSLEOFErrorObject); - Py_VISIT(state->lib_codes_to_names); Py_VISIT(state->Sock_Type); return 0; @@ -7099,8 +7125,10 @@ sslmodule_clear(PyObject *m) _Py_hashtable_destroy(state->err_codes_to_names); state->err_codes_to_names = NULL; } - - Py_CLEAR(state->lib_codes_to_names); + if (state->lib_codes_to_names != NULL) { + _Py_hashtable_destroy(state->lib_codes_to_names); + state->lib_codes_to_names = NULL; + } Py_CLEAR(state->Sock_Type); Py_CLEAR(state->str_library); Py_CLEAR(state->str_reason); diff --git a/Modules/_ssl.h b/Modules/_ssl.h index 28cc2ab7a93026..5b81e420f7d55b 100644 --- a/Modules/_ssl.h +++ b/Modules/_ssl.h @@ -27,7 +27,7 @@ typedef struct { PyObject *PySSLEOFErrorObject; /* Error mappings */ _Py_hashtable_t *err_codes_to_names; - PyObject *lib_codes_to_names; + _Py_hashtable_t *lib_codes_to_names; /* socket type from module CAPI */ PyTypeObject *Sock_Type; /* Interned strings */ From 4a2ed1d558a90cbe7f43088ddb41528491fb450e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:47:39 +0100 Subject: [PATCH 17/38] post-merge --- Modules/_ssl.c | 61 ++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index c8c06ad85e1f0e..280fd4158eafdd 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -466,40 +466,47 @@ static PyType_Spec sslerror_type_spec = { .slots = sslerror_type_slots }; +/* + * Convert an error code in openssl/err.h to a hash table key. + * + * The 'code' may be a single field component (library, function + * or reason) given as an `int` or a packed error code given as + * an `unsigned long`. + * + * We always assume that 'code' is non-negative. + */ +static inline const void * +ssl_errcode_to_ht_key(ssize_t code) +{ + assert(code >= 0); /* individual codes are int but always >= 0*/ + return ((const void *)((uintptr_t)(code))); +} + /* * Get the library and reason strings from a packed error code. * * This stores NULL or new references to Unicode objects in 'lib' and 'reason', * and thus the caller is responsible for calling Py_XDECREF() on them. * - * This returns 0 if both 'lib' and 'reason' were successfully set (possibly - * to NULL), and -1 otherwise. + * This function always succeeds. */ -static int +static void ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, PyObject **lib, PyObject **reason) { - int errlib = ERR_GET_LIB(errcode); - int errrea = ERR_GET_REASON(errcode); + const void *key, *val; + int libcode = ERR_GET_LIB(errcode); + int reacode = ERR_GET_REASON(errcode); - const void *key1 = (const void *)((uintptr_t)errlib); - const void *val1 = _Py_hashtable_get(state->lib_codes_to_names, key1); - if (val1 == NULL) { - *lib = NULL; - return -1; - } - assert(PyUnicode_CheckExact(val1)); - *lib = Py_NewRef((PyObject *)val1); + key = ssl_errcode_to_ht_key(libcode); + val = (PyObject *)_Py_hashtable_get(state->lib_codes_to_names, key); + assert(val == NULL || PyUnicode_CheckExact(val)); + *lib = Py_XNewRef(val); - const void *key2 = (const void *)((uintptr_t)ERR_PACK(errlib, 0, errrea)); - const void *val2 = _Py_hashtable_get(state->err_codes_to_names, key2); - if (val2 == NULL) { - *reason = NULL; - return -1; - } - assert(PyUnicode_CheckExact(val2)); - *reason = Py_NewRef((PyObject *)val2); - return 0; + key = ssl_errcode_to_ht_key(ERR_PACK(libcode, 0UL, reacode)); + val = (PyObject *)_Py_hashtable_get(state->err_codes_to_names, key); + assert(val == NULL || PyUnicode_CheckExact(val)); + *reason = Py_XNewRef(val); } /* @@ -749,11 +756,7 @@ fill_and_set_sslerror(_sslmodulestate *state, { PyObject *lib = NULL, *reason = NULL; if (errcode) { - if (ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason) < 0) { - Py_XDECREF(reason); - Py_XDECREF(lib); - return; - } + ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason); } if (errstr == NULL) { errstr = errcode @@ -6855,7 +6858,7 @@ py_ht_errcode_to_name_create(void) { for (const py_ssl_error_code *p = error_codes; p->mnemonic != NULL; p++) { py_ssl_errcode code = ERR_PACK(p->library, 0, p->reason); - const void *key = (const void *)((uintptr_t)code); + const void *key = ssl_errcode_to_ht_key(code); PyObject *value = PyUnicode_FromString(p->mnemonic); if (value == NULL) { goto error; @@ -6903,7 +6906,7 @@ py_ht_libcode_to_name_create(void) { } for (const py_ssl_library_code *p = library_codes; p->library != NULL; p++) { - const void *key = (const void *)((uintptr_t)p->code); + const void *key = ssl_errcode_to_ht_key(p->code); PyObject *value = PyUnicode_FromString(p->library); if (value == NULL) { goto error; From 630c2b5cbd89a9958f82582234f596179f05222f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 13:57:02 +0100 Subject: [PATCH 18/38] fix compilation? --- Modules/_ssl.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 280fd4158eafdd..866968475bbbe5 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -475,12 +475,7 @@ static PyType_Spec sslerror_type_spec = { * * We always assume that 'code' is non-negative. */ -static inline const void * -ssl_errcode_to_ht_key(ssize_t code) -{ - assert(code >= 0); /* individual codes are int but always >= 0*/ - return ((const void *)((uintptr_t)(code))); -} +#define ssl_errcode_to_ht_key(code) ((const void *)((uintptr_t)(code))) /* * Get the library and reason strings from a packed error code. @@ -503,7 +498,7 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, assert(val == NULL || PyUnicode_CheckExact(val)); *lib = Py_XNewRef(val); - key = ssl_errcode_to_ht_key(ERR_PACK(libcode, 0UL, reacode)); + key = ssl_errcode_to_ht_key(ERR_PACK(libcode, 0, reacode)); val = (PyObject *)_Py_hashtable_get(state->err_codes_to_names, key); assert(val == NULL || PyUnicode_CheckExact(val)); *reason = Py_XNewRef(val); From 98c8b9914ab77a59b6cd62cd7b1e03ee5a3f6887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:24:44 +0100 Subject: [PATCH 19/38] attempt various things --- Modules/_ssl.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 866968475bbbe5..deb8ec1f6c744f 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6821,14 +6821,12 @@ sslmodule_init_constants(PyObject *m) /* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ static Py_uhash_t py_ht_errcode_to_name_hash(const void *key) { - return (Py_uhash_t)(py_ssl_errcode)((uintptr_t)key); + return _Py_hashtable_hash_ptr(key); } static int py_ht_errcode_to_name_comp(const void *a, const void *b) { - py_ssl_errcode a_code = (py_ssl_errcode)((uintptr_t)a); - py_ssl_errcode b_code = (py_ssl_errcode)((uintptr_t)b); - return a_code == b_code; + return _Py_hashtable_hash_ptr(a) == _Py_hashtable_hash_ptr(b); } static void From 133ec9f6d221ac5c8c8f58de703290ad1445abac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:40:26 +0100 Subject: [PATCH 20/38] try even more things! --- Modules/_ssl.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index deb8ec1f6c744f..2cf5fb4dbb9543 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6819,15 +6819,6 @@ sslmodule_init_constants(PyObject *m) } /* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ -static Py_uhash_t -py_ht_errcode_to_name_hash(const void *key) { - return _Py_hashtable_hash_ptr(key); -} - -static int -py_ht_errcode_to_name_comp(const void *a, const void *b) { - return _Py_hashtable_hash_ptr(a) == _Py_hashtable_hash_ptr(b); -} static void py_ht_errcode_to_name_free(void *value) { @@ -6838,8 +6829,8 @@ py_ht_errcode_to_name_free(void *value) { static _Py_hashtable_t * py_ht_errcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( - py_ht_errcode_to_name_hash, - py_ht_errcode_to_name_comp, + _Py_hashtable_hash_ptr, + _Py_hashtable_compare_direct, NULL, py_ht_errcode_to_name_free, NULL @@ -6868,15 +6859,6 @@ py_ht_errcode_to_name_create(void) { } /* internal hashtable (libcode) => (libname [PyObject * (unicode)]) */ -static Py_uhash_t -py_ht_libcode_to_name_hash(const void *key) { - return (Py_uhash_t)((uintptr_t)key); -} - -static int -py_ht_libcode_to_name_comp(const void *a, const void *b) { - return (uintptr_t)a == (uintptr_t)b; -} static void py_ht_libcode_to_name_free(void *value) { @@ -6887,8 +6869,8 @@ py_ht_libcode_to_name_free(void *value) { static _Py_hashtable_t * py_ht_libcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( - py_ht_libcode_to_name_hash, - py_ht_libcode_to_name_comp, + _Py_hashtable_hash_ptr, + _Py_hashtable_compare_direct, NULL, py_ht_libcode_to_name_free, NULL From fb5d80010b9169415a1410852289784a2f63b6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:46:42 +0100 Subject: [PATCH 21/38] :@ --- Modules/_ssl.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 2cf5fb4dbb9543..36982f3ab64f36 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6847,6 +6847,11 @@ py_ht_errcode_to_name_create(void) { if (value == NULL) { goto error; } + PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ + if (prev != NULL) { + printf("oh no for: %s (%d, %d)", p->mnemonic, p->library, p->reason); + _PyObject_Dump(prev); + } if (_Py_hashtable_set(table, key, value) < 0) { Py_DECREF(value); goto error; @@ -6886,6 +6891,11 @@ py_ht_libcode_to_name_create(void) { if (value == NULL) { goto error; } + PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ + if (prev != NULL) { + printf("oh no: %s (%d)\n", p->library, p->code); + _PyObject_Dump(prev); + } if (_Py_hashtable_set(table, key, value) < 0) { Py_DECREF(value); goto error; From d6a052953bf54eb98e166a27d649495e4adbc6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:52:04 +0100 Subject: [PATCH 22/38] :@@@@@@@@@@@@@@ --- Modules/_ssl.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 36982f3ab64f36..578907caf93d3a 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6849,7 +6849,9 @@ py_ht_errcode_to_name_create(void) { } PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ if (prev != NULL) { - printf("oh no for: %s (%d, %d)", p->mnemonic, p->library, p->reason); + PyObject *msg = PyUnicode_FromFormat("oh no for: %s (%d, %d)", p->mnemonic, p->library, p->reason); + _PyObject_Dump(msg); + Py_DECREF(msg); _PyObject_Dump(prev); } if (_Py_hashtable_set(table, key, value) < 0) { @@ -6893,7 +6895,9 @@ py_ht_libcode_to_name_create(void) { } PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ if (prev != NULL) { - printf("oh no: %s (%d)\n", p->library, p->code); + PyObject *msg = PyUnicode_FromFormat("oh no: %s (%d)", p->library, p->code); + _PyObject_Dump(msg); + Py_DECREF(msg); _PyObject_Dump(prev); } if (_Py_hashtable_set(table, key, value) < 0) { From bb043b2c45dc2df34866f65b1e9c92d7b0de0434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:09:57 +0100 Subject: [PATCH 23/38] skip duplicated entries if they are correct --- Modules/_ssl.c | 40 +++++++++++++++++++++++++------------- Tools/ssl/make_ssl_data.py | 2 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 578907caf93d3a..fd57637a039022 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6843,17 +6843,23 @@ py_ht_errcode_to_name_create(void) { for (const py_ssl_error_code *p = error_codes; p->mnemonic != NULL; p++) { py_ssl_errcode code = ERR_PACK(p->library, 0, p->reason); const void *key = ssl_errcode_to_ht_key(code); + PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ + if (prev != NULL) { + assert(PyUnicode_CheckExact(prev)); + if (PyUnicode_EqualToUTF8(prev, p->mnemonic)) { + /* sometimes data is duplicated, so we skip it */ + continue; + } + PyErr_Format(PyExc_SystemError, + "SSL data contains incompatible entries for (%d, %d). " + "Old mnemonic is %S while new mnemonic is %s", + p->library, p->reason, prev, p->mnemonic); + goto error; + } PyObject *value = PyUnicode_FromString(p->mnemonic); if (value == NULL) { goto error; } - PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ - if (prev != NULL) { - PyObject *msg = PyUnicode_FromFormat("oh no for: %s (%d, %d)", p->mnemonic, p->library, p->reason); - _PyObject_Dump(msg); - Py_DECREF(msg); - _PyObject_Dump(prev); - } if (_Py_hashtable_set(table, key, value) < 0) { Py_DECREF(value); goto error; @@ -6889,17 +6895,23 @@ py_ht_libcode_to_name_create(void) { for (const py_ssl_library_code *p = library_codes; p->library != NULL; p++) { const void *key = ssl_errcode_to_ht_key(p->code); + PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ + if (prev != NULL) { + assert(PyUnicode_CheckExact(prev)); + if (PyUnicode_EqualToUTF8(prev, p->library)) { + /* sometimes data is duplicated, so we skip it */ + continue; + } + PyErr_Format(PyExc_SystemError, + "SSL data contains incompatible entries for %d. " + "Old library is %S while new library is %s.", + p->code, prev, p->library); + goto error; + } PyObject *value = PyUnicode_FromString(p->library); if (value == NULL) { goto error; } - PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ - if (prev != NULL) { - PyObject *msg = PyUnicode_FromFormat("oh no: %s (%d)", p->library, p->code); - _PyObject_Dump(msg); - Py_DECREF(msg); - _PyObject_Dump(prev); - } if (_Py_hashtable_set(table, key, value) < 0) { Py_DECREF(value); goto error; diff --git a/Tools/ssl/make_ssl_data.py b/Tools/ssl/make_ssl_data.py index da05d2bc8b9752..103ca8d0d7634c 100755 --- a/Tools/ssl/make_ssl_data.py +++ b/Tools/ssl/make_ssl_data.py @@ -135,7 +135,7 @@ def main(): reasons.extend(parse_openssl_error_text(args)) reasons.extend(parse_extra_reasons(args)) # sort by libname, numeric error code - args.reasons = sorted(reasons, key=operator.itemgetter(0, 3)) + args.reasons = sorted(set(reasons), key=operator.itemgetter(0, 3)) git_describe = subprocess.run( ['git', 'describe', '--long', '--dirty'], From f836684203b93ca373622141ea05de8de614cb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:22:11 +0100 Subject: [PATCH 24/38] log more stuff --- Modules/_ssl.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index fd57637a039022..ea66d6df0fc840 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6851,9 +6851,10 @@ py_ht_errcode_to_name_create(void) { continue; } PyErr_Format(PyExc_SystemError, - "SSL data contains incompatible entries for (%d, %d). " - "Old mnemonic is %S while new mnemonic is %s", - p->library, p->reason, prev, p->mnemonic); + "SSL data contains incompatible entries for " + "(%d, %d; %p, %ld). Old mnemonic is %S but " + "new mnemonic is %s", + p->library, p->reason, key, code, prev, p->mnemonic); goto error; } PyObject *value = PyUnicode_FromString(p->mnemonic); @@ -6903,9 +6904,10 @@ py_ht_libcode_to_name_create(void) { continue; } PyErr_Format(PyExc_SystemError, - "SSL data contains incompatible entries for %d. " - "Old library is %S while new library is %s.", - p->code, prev, p->library); + "SSL data contains incompatible entries for " + "(%p; %d). Old library is %S but new library " + "is %s.", + key, p->code, prev, p->library); goto error; } PyObject *value = PyUnicode_FromString(p->library); From db3ed1cc4d3a473486a28949a74c66d3c26fda1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 4 Jan 2025 10:49:56 +0100 Subject: [PATCH 25/38] change hash function key comparator? --- Modules/_ssl.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index ea66d6df0fc840..2f87a6d5ee5ec0 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -475,7 +475,12 @@ static PyType_Spec sslerror_type_spec = { * * We always assume that 'code' is non-negative. */ -#define ssl_errcode_to_ht_key(code) ((const void *)((uintptr_t)(code))) +static inline const void * +ssl_errcode_to_ht_key(ssize_t code) +{ + assert(code >= 0); + return ((const void *)((uintptr_t)(code))); +} /* * Get the library and reason strings from a packed error code. @@ -6820,6 +6825,12 @@ sslmodule_init_constants(PyObject *m) /* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ +static int +py_ht_errcode_to_name_comp(const void *k1, const void *k2) +{ + return (uintptr_t)k1 == (uintptr_t)k2; +} + static void py_ht_errcode_to_name_free(void *value) { assert(PyUnicode_CheckExact((PyObject *)value)); @@ -6830,7 +6841,7 @@ static _Py_hashtable_t * py_ht_errcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( _Py_hashtable_hash_ptr, - _Py_hashtable_compare_direct, + py_ht_errcode_to_name_comp, NULL, py_ht_errcode_to_name_free, NULL @@ -6874,6 +6885,12 @@ py_ht_errcode_to_name_create(void) { /* internal hashtable (libcode) => (libname [PyObject * (unicode)]) */ +static int +py_ht_libcode_to_name_comp(const void *k1, const void *k2) +{ + return (uintptr_t)k1 == (uintptr_t)k2; +} + static void py_ht_libcode_to_name_free(void *value) { assert(PyUnicode_CheckExact((PyObject *)value)); @@ -6884,7 +6901,7 @@ static _Py_hashtable_t * py_ht_libcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( _Py_hashtable_hash_ptr, - _Py_hashtable_compare_direct, + py_ht_libcode_to_name_comp, NULL, py_ht_libcode_to_name_free, NULL From 7a198d4bbd060b78919121dce620578c6636038f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 4 Jan 2025 10:58:46 +0100 Subject: [PATCH 26/38] revert macro --- Modules/_ssl.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 2f87a6d5ee5ec0..37e9b433a99633 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -475,12 +475,7 @@ static PyType_Spec sslerror_type_spec = { * * We always assume that 'code' is non-negative. */ -static inline const void * -ssl_errcode_to_ht_key(ssize_t code) -{ - assert(code >= 0); - return ((const void *)((uintptr_t)(code))); -} +#define ssl_errcode_to_ht_key(code) ((const void *)((uintptr_t)(code))) /* * Get the library and reason strings from a packed error code. @@ -6914,6 +6909,7 @@ py_ht_libcode_to_name_create(void) { for (const py_ssl_library_code *p = library_codes; p->library != NULL; p++) { const void *key = ssl_errcode_to_ht_key(p->code); PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ + printf("") if (prev != NULL) { assert(PyUnicode_CheckExact(prev)); if (PyUnicode_EqualToUTF8(prev, p->library)) { From 93a7a9bc205e47e5126489966c80f8f60493e0ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:14:10 +0100 Subject: [PATCH 27/38] full debug --- Modules/_ssl.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 37e9b433a99633..0727c71fb81988 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6820,6 +6820,13 @@ sslmodule_init_constants(PyObject *m) /* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ +static Py_uhash_t +py_ht_errcode_to_name_hash(const void *key) +{ + py_ssl_errcode code = (py_ssl_errcode)(uintptr_t)key; + return (Py_uhash_t)(code); +} + static int py_ht_errcode_to_name_comp(const void *k1, const void *k2) { @@ -6828,14 +6835,14 @@ py_ht_errcode_to_name_comp(const void *k1, const void *k2) static void py_ht_errcode_to_name_free(void *value) { - assert(PyUnicode_CheckExact((PyObject *)value)); - Py_CLEAR(value); + // assert(PyUnicode_CheckExact((PyObject *)value)); + // Py_CLEAR(value); } static _Py_hashtable_t * py_ht_errcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( - _Py_hashtable_hash_ptr, + py_ht_errcode_to_name_hash, py_ht_errcode_to_name_comp, NULL, py_ht_errcode_to_name_free, @@ -6847,10 +6854,14 @@ py_ht_errcode_to_name_create(void) { } for (const py_ssl_error_code *p = error_codes; p->mnemonic != NULL; p++) { + int toshow = p->library == 41 && (p->reason == 103 || p->reason == 106); + if (toshow) printf("%s ", p->mnemonic); py_ssl_errcode code = ERR_PACK(p->library, 0, p->reason); const void *key = ssl_errcode_to_ht_key(code); + if (toshow) printf("%d, %d, %p, %ld, %ld\n", p->library, p->reason, key, code, (uintptr_t)code); PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ if (prev != NULL) { + printf("old: %s (%d, %d, %ld)\n", p->mnemonic, p->library, p->reason, code); assert(PyUnicode_CheckExact(prev)); if (PyUnicode_EqualToUTF8(prev, p->mnemonic)) { /* sometimes data is duplicated, so we skip it */ @@ -6909,7 +6920,6 @@ py_ht_libcode_to_name_create(void) { for (const py_ssl_library_code *p = library_codes; p->library != NULL; p++) { const void *key = ssl_errcode_to_ht_key(p->code); PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ - printf("") if (prev != NULL) { assert(PyUnicode_CheckExact(prev)); if (PyUnicode_EqualToUTF8(prev, p->library)) { From 95e102168c3da3ef190cd907364f5ce1a969d5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:15:12 +0100 Subject: [PATCH 28/38] remove un-necessary specializations --- Modules/_ssl.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 0727c71fb81988..70009b45f2297f 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6820,30 +6820,17 @@ sslmodule_init_constants(PyObject *m) /* internal hashtable (errcode, libcode) => (reason [PyObject * (unicode)]) */ -static Py_uhash_t -py_ht_errcode_to_name_hash(const void *key) -{ - py_ssl_errcode code = (py_ssl_errcode)(uintptr_t)key; - return (Py_uhash_t)(code); -} - -static int -py_ht_errcode_to_name_comp(const void *k1, const void *k2) -{ - return (uintptr_t)k1 == (uintptr_t)k2; -} - static void py_ht_errcode_to_name_free(void *value) { - // assert(PyUnicode_CheckExact((PyObject *)value)); - // Py_CLEAR(value); + assert(PyUnicode_CheckExact((PyObject *)value)); + Py_CLEAR(value); } static _Py_hashtable_t * py_ht_errcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( - py_ht_errcode_to_name_hash, - py_ht_errcode_to_name_comp, + _Py_hashtable_hash_ptr, + _Py_hashtable_compare_direct, NULL, py_ht_errcode_to_name_free, NULL @@ -6891,12 +6878,6 @@ py_ht_errcode_to_name_create(void) { /* internal hashtable (libcode) => (libname [PyObject * (unicode)]) */ -static int -py_ht_libcode_to_name_comp(const void *k1, const void *k2) -{ - return (uintptr_t)k1 == (uintptr_t)k2; -} - static void py_ht_libcode_to_name_free(void *value) { assert(PyUnicode_CheckExact((PyObject *)value)); @@ -6907,7 +6888,7 @@ static _Py_hashtable_t * py_ht_libcode_to_name_create(void) { _Py_hashtable_t *table = _Py_hashtable_new_full( _Py_hashtable_hash_ptr, - py_ht_libcode_to_name_comp, + _Py_hashtable_compare_direct, NULL, py_ht_libcode_to_name_free, NULL From b8f0cce98ec038c912c4931a12aa62d7b004d9c1 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, 5 Jan 2025 11:45:22 +0100 Subject: [PATCH 29/38] select when to use verbose SSL errors --- Modules/_ssl.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 70009b45f2297f..0fa61550775fba 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -477,6 +477,31 @@ static PyType_Spec sslerror_type_spec = { */ #define ssl_errcode_to_ht_key(code) ((const void *)((uintptr_t)(code))) +/* + * Determine whether the SSL formatted exception needs to be verbose. + * + * This is only used to avoid adding un-necessary attributes or building + * extremely verbose error messages which could slow the SSL handshake. + * + * See https://github.com/python/cpython/issues/123954 for details. + */ +static int +ssl_use_verbose_error(_sslmodulestate *state, PyObject *exc_type, + int Py_UNUSED(ssl_errno), py_ssl_errcode errcode) +{ + if (errcode == 0) { + return 0; + } + + if ( + exc_type == state->PySSLWantReadErrorObject + || exc_type == state->PySSLWantWriteErrorObject + ) { + return 0; + } + return 1; +} + /* * Get the library and reason strings from a packed error code. * @@ -750,7 +775,7 @@ fill_and_set_sslerror(_sslmodulestate *state, const char *filename, int lineno) { PyObject *lib = NULL, *reason = NULL; - if (errcode) { + if (ssl_use_verbose_error(state, exc_type, ssl_errno, errcode)) { ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason); } if (errstr == NULL) { From 689568f0cec4a8eed3bb83d7ff31e785f5d091b1 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, 5 Jan 2025 11:56:57 +0100 Subject: [PATCH 30/38] reduce the number of format units --- Modules/_ssl.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 0fa61550775fba..551dbca78ca49e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -694,26 +694,26 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err /* verify code for cert validation error */ long verify_code_value = SSL_get_verify_result(sslsock->ssl); - const char *what = - verify_code_value == X509_V_ERR_HOSTNAME_MISMATCH - ? "Hostname" - : verify_code_value == X509_V_ERR_IP_ADDRESS_MISMATCH - ? "IP address" - : NULL; - - if (what == NULL) { - const char *s = X509_verify_cert_error_string(verify_code_value); - verify = s == NULL ? Py_None : PyUnicode_FromString(s); - } - else { - // The server's hostname is known to be an ASCII string. - assert(PyUnicode_IS_ASCII(sslsock->server_hostname)); - const char *hostname = PyUnicode_AsUTF8(sslsock->server_hostname); - verify = PyUnicode_FromFormat( - "%s mismatch, certificate is not valid for '%s'.", - what, hostname - ); + switch (verify_code_value) { + case X509_V_ERR_IP_ADDRESS_MISMATCH: _Py_FALLTHROUGH; + case X509_V_ERR_HOSTNAME_MISMATCH: { + // The server's hostname is known to be an ASCII string. + assert(PyUnicode_IS_ASCII(sslsock->server_hostname)); + const char *hostname = PyUnicode_AsUTF8(sslsock->server_hostname); + const char *fmt = + verify_code_value == X509_V_ERR_IP_ADDRESS_MISMATCH + ? "IP address mismatch, certificate is not valid for '%s'." + : "Hostname mismatch, certificate is not valid for '%s'."; + verify = PyUnicode_FromFormat(fmt, hostname); + break; + } + default: { + const char *s = X509_verify_cert_error_string(verify_code_value); + verify = s == NULL ? Py_None : PyUnicode_FromString(s); + break; + } } + if (verify == NULL) { goto fail; } From 5583aa54f40d06e30f524a7a4f1da7c0030ad896 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, 5 Jan 2025 12:14:17 +0100 Subject: [PATCH 31/38] optimize `format_ssl_error_message` --- Modules/_ssl.c | 148 ++++++++++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 56 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 551dbca78ca49e..68e6f002c1bedf 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -529,6 +529,40 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, *reason = Py_XNewRef(val); } +static inline size_t +ssl_error_filename_width(const char *Py_UNUSED(filename)) +{ + /* + * Sometimes, __FILE__ is an absolute path, so we hardcode "_ssl.c". + * In the future, we might want to use the "filename" parameter but + * for now (and for backward compatibility), we ignore it. + */ + return 6 /* strlen("_ssl.c") */; +} + +static inline size_t +ssl_error_lineno_width(int lineno) +{ + if (lineno < 0) { + return 1 + ssl_error_lineno_width(-lineno); + } +#define FAST_PATH(E, N) \ + do { \ + assert((size_t)(1e ## E) == N); \ + if (lineno < (N)) { \ + return (E); \ + } \ + } while (0) + FAST_PATH(2, 10); + FAST_PATH(3, 100); + FAST_PATH(4, 1000); + FAST_PATH(5, 10000); + FAST_PATH(6, 100000); + FAST_PATH(7, 1000000); +#undef FAST_PATH + return (size_t)ceil(log10(lineno)); +} + /* * Construct a Unicode object containing the formatted SSL error message. * @@ -537,7 +571,7 @@ ssl_error_fetch_lib_and_reason(_sslmodulestate *state, py_ssl_errcode errcode, static PyObject * format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, const char *errstr, - const char *Py_UNUSED(filename), int lineno) + const char *filename, int lineno) { assert(errstr != NULL); @@ -551,71 +585,71 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, CHECK_OBJECT(verify); #undef CHECK_OBJECT -#define OPTIONAL_UTF8(x) ((x) == NULL ? NULL : PyUnicode_AsUTF8((x))) - const char *libstr = OPTIONAL_UTF8(lib); - const char *reastr = OPTIONAL_UTF8(reason); - const char *verstr = OPTIONAL_UTF8(verify); -#undef OPTIONAL_UTF8 - - const size_t errstr_len = strlen(errstr); - const size_t libstr_len = libstr == NULL ? 0 : strlen(libstr); - const size_t reastr_len = reastr == NULL ? 0 : strlen(reastr); - const size_t verstr_len = verstr == NULL ? 0 : strlen(verstr); - /* - * Sometimes, __FILE__ is an absolute path, so we hardcode "_ssl.c". - * In the future, we might want to use the "filename" parameter but - * for now (and for backward compatibility), we ignore it. - */ - const size_t filename_len = 6; /* strlen("_ssl.c") */ - /* - * Crude upper bound on the number of characters taken by the line number. - * We expect -1 <= lineno < 1e8. More lines are unlikely to happen. - */ - assert(lineno >= -1); - assert(lineno < 1e8); - const size_t lineno_len = 8; - const size_t base_alloc = ( - libstr_len + reastr_len + verstr_len - + errstr_len + filename_len + lineno_len - ); + const size_t filename_len = ssl_error_filename_width(filename); + const size_t lineno_len = ssl_error_lineno_width(lineno); + /* exact length of "(_ssl.c:LINENO)" */ + const size_t suffix_len = 1 + filename_len + 1 + lineno_len + 1; int rc; - char *buf; - + PyObject *res = NULL; +#define CSTRBUF(x) ((const char *)PyUnicode_DATA((x))) +#define CHARBUF(x) ((char *)PyUnicode_DATA((x))) if (lib && reason && verify) { /* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */ - const size_t alloc = base_alloc + (4 + 3 + 4); - /* PyMem_Malloc() is optimized for small buffers */ - buf = PyMem_New(char, alloc); - rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s: %s (_ssl.c:%d)", - libstr, reastr, errstr, verstr, lineno); + const char *lib_cstr = CSTRBUF(lib); + const char *reason_cstr = CSTRBUF(reason); + const char *verify_cstr = CSTRBUF(verify); + const size_t ressize = /* excludes final null byte */ ( + 1 + strlen(lib_cstr) + 2 + strlen(reason_cstr) + 1 + + 1 + strlen(errstr) + 2 + strlen(verify_cstr) + + 1 + suffix_len + ); + res = PyUnicode_New(ressize, 127); + rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s: %s (_ssl.c:%d)", + lib_cstr, reason_cstr, errstr, verify_cstr, lineno); } else if (lib && reason) { /* [LIB: REASON] ERROR (_ssl.c:LINENO) */ - const size_t alloc = base_alloc + (3 + 2 + 4); - buf = PyMem_New(char, alloc); - rc = PyOS_snprintf(buf, alloc, "[%s: %s] %s (_ssl.c:%d)", - libstr, reastr, errstr, lineno); + const char *lib_cstr = CSTRBUF(lib); + const char *reason_cstr = CSTRBUF(reason); + const size_t ressize = /* excludes final null byte */ ( + 1 + strlen(lib_cstr) + 2 + strlen(reason_cstr) + 1 + + 1 + strlen(errstr) + + 1 + suffix_len + ); + res = PyUnicode_New(ressize, 127); + rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s (_ssl.c:%d)", + lib_cstr, reason_cstr, errstr, lineno); } else if (lib) { /* [LIB] ERROR (_ssl.c:LINENO) */ - const size_t alloc = base_alloc + (2 + 1 + 4); - buf = PyMem_New(char, alloc); - rc = PyOS_snprintf(buf, alloc, "[%s] %s (_ssl.c:%d)", - libstr, errstr, lineno); + const char *lib_cstr = CSTRBUF(lib); + const size_t ressize = /* excludes final null byte */ ( + 1 + strlen(lib_cstr) + 1 + + 1 + strlen(errstr) + + 1 + suffix_len + ); + res = PyUnicode_New(ressize, 127); + rc = snprintf(CHARBUF(res), ressize + 1, "[%s] %s (_ssl.c:%d)", + lib_cstr, errstr, lineno); } else { /* ERROR (_ssl.c:LINENO) */ - const size_t alloc = base_alloc + (1 + 1 + 2); - buf = PyMem_New(char, alloc); - rc = PyOS_snprintf(buf, alloc, "%s (_ssl.c:%d)", - errstr, lineno); + const size_t ressize = /* excludes final null byte */ ( + strlen(errstr) + + 1 + suffix_len + ); + res = PyUnicode_New(ressize, 127); + rc = snprintf(CHARBUF(res), ressize + 1, "%s (_ssl.c:%d)", + errstr, lineno); + } +#undef CHARBUF +#undef CSTRBUF + if (rc < 0) { + Py_XDECREF(res); + /* fallback to slow path if snprintf() failed */ + return PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno); } - - PyObject *res = rc < 0 /* fallback to slow path if snprintf() failed */ - ? PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno) - : PyUnicode_FromString(buf) /* uses the ASCII fast path */; - PyMem_Free(buf); return res; } @@ -628,7 +662,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, * ssl_errno The SSL error number to pass to the exception constructor. * lib The ASCII-encoded library obtained from a packed error code. * reason The ASCII-encoded reason obtained from a packed error code. - * errstr The error message to use. + * errstr The non-NULL error message to use. * * A non-NULL library or reason is stored in the final exception object. */ @@ -778,10 +812,12 @@ fill_and_set_sslerror(_sslmodulestate *state, if (ssl_use_verbose_error(state, exc_type, ssl_errno, errcode)) { ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason); } + if (errstr == NULL && errcode) { + errstr = ERR_reason_error_string(errcode); + } if (errstr == NULL) { - errstr = errcode - ? ERR_reason_error_string(errcode) - : "unknown error"; + // ERR_reason_error_string() may return NULL + errstr = "unknown error"; } PyObject *exc; if (sslsock != NULL && exc_type == state->PySSLCertVerificationErrorObject) { From a0ab1c29cdad036285741920368493a4c3e346ae 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, 5 Jan 2025 15:36:58 +0100 Subject: [PATCH 32/38] temporarily patch CI --- Modules/_ssl.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 68e6f002c1bedf..4b4cabe828f443 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6902,25 +6902,21 @@ py_ht_errcode_to_name_create(void) { } for (const py_ssl_error_code *p = error_codes; p->mnemonic != NULL; p++) { - int toshow = p->library == 41 && (p->reason == 103 || p->reason == 106); - if (toshow) printf("%s ", p->mnemonic); py_ssl_errcode code = ERR_PACK(p->library, 0, p->reason); const void *key = ssl_errcode_to_ht_key(code); - if (toshow) printf("%d, %d, %p, %ld, %ld\n", p->library, p->reason, key, code, (uintptr_t)code); PyObject *prev = _Py_hashtable_get(table, key); /* borrowed */ if (prev != NULL) { - printf("old: %s (%d, %d, %ld)\n", p->mnemonic, p->library, p->reason, code); assert(PyUnicode_CheckExact(prev)); if (PyUnicode_EqualToUTF8(prev, p->mnemonic)) { /* sometimes data is duplicated, so we skip it */ continue; } - PyErr_Format(PyExc_SystemError, - "SSL data contains incompatible entries for " - "(%d, %d; %p, %ld). Old mnemonic is %S but " - "new mnemonic is %s", - p->library, p->reason, key, code, prev, p->mnemonic); - goto error; + PyErr_WarnFormat(PyExc_RuntimeWarning, 2, + "SSL data contains incompatible entries for " + "(%d, %d; %p, %ld). Old mnemonic is %S but " + "new mnemonic is %s.", + p->library, p->reason, key, code, prev, p->mnemonic); + continue; } PyObject *value = PyUnicode_FromString(p->mnemonic); if (value == NULL) { @@ -6968,12 +6964,12 @@ py_ht_libcode_to_name_create(void) { /* sometimes data is duplicated, so we skip it */ continue; } - PyErr_Format(PyExc_SystemError, - "SSL data contains incompatible entries for " - "(%p; %d). Old library is %S but new library " - "is %s.", - key, p->code, prev, p->library); - goto error; + PyErr_WarnFormat(PyExc_RuntimeWarning, 2, + "SSL data contains incompatible entries for " + "(%p; %d). Old library is %S but new library " + "is %s.", + key, p->code, prev, p->library); + continue; } PyObject *value = PyUnicode_FromString(p->library); if (value == NULL) { From 6b38769accf8f324d480b9a626c2cd74604bdba5 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, 5 Jan 2025 15:40:05 +0100 Subject: [PATCH 33/38] fix assertions --- Modules/_ssl.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 4b4cabe828f443..dd9417a2233dc9 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -553,12 +553,13 @@ ssl_error_lineno_width(int lineno) return (E); \ } \ } while (0) - FAST_PATH(2, 10); - FAST_PATH(3, 100); - FAST_PATH(4, 1000); - FAST_PATH(5, 10000); - FAST_PATH(6, 100000); - FAST_PATH(7, 1000000); + FAST_PATH(1, 10); + FAST_PATH(2, 100); + FAST_PATH(3, 1000); + FAST_PATH(4, 10000); + FAST_PATH(5, 100000); + FAST_PATH(6, 1000000); + FAST_PATH(7, 10000000); #undef FAST_PATH return (size_t)ceil(log10(lineno)); } From 3ecba486ee1f5232526e20cd9c14fbd6c6bdd109 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, 5 Jan 2025 15:58:58 +0100 Subject: [PATCH 34/38] now fix assertions correctly --- Modules/_ssl.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index dd9417a2233dc9..b7242b832fe7e2 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -560,8 +560,14 @@ ssl_error_lineno_width(int lineno) FAST_PATH(5, 100000); FAST_PATH(6, 1000000); FAST_PATH(7, 10000000); + FAST_PATH(8, 100000000); #undef FAST_PATH - return (size_t)ceil(log10(lineno)); + /* + * log10() is imprecise above 10^14, but it's not a realistic case. + * Even with those imprecisions, we will overestimate the actual + * number of characters needed to render 'lineno'. + */ + return 1 + (size_t)log10(lineno); } /* From d7f1ab18ce3b4b4144e71624e74239103819a93e 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, 5 Jan 2025 16:09:44 +0100 Subject: [PATCH 35/38] use ImportWarning insteaad of RuntimeWarning --- Modules/_ssl.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index b7242b832fe7e2..8aec3b0ad7d309 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6918,11 +6918,15 @@ py_ht_errcode_to_name_create(void) { /* sometimes data is duplicated, so we skip it */ continue; } - PyErr_WarnFormat(PyExc_RuntimeWarning, 2, - "SSL data contains incompatible entries for " - "(%d, %d; %p, %ld). Old mnemonic is %S but " - "new mnemonic is %s.", - p->library, p->reason, key, code, prev, p->mnemonic); + int rc = PyErr_WarnFormat(PyExc_ImportWarning, 2, + "SSL data contains incompatible entries " + "for (%d, %d; %p, %ld). Old mnemonic is " + "%S but new mnemonic is %s.", + p->library, p->reason, key, code, + prev, p->mnemonic); + if (rc < 0) { + goto error; + } continue; } PyObject *value = PyUnicode_FromString(p->mnemonic); @@ -6971,11 +6975,14 @@ py_ht_libcode_to_name_create(void) { /* sometimes data is duplicated, so we skip it */ continue; } - PyErr_WarnFormat(PyExc_RuntimeWarning, 2, - "SSL data contains incompatible entries for " - "(%p; %d). Old library is %S but new library " - "is %s.", - key, p->code, prev, p->library); + int rc = PyErr_WarnFormat(PyExc_ImportWarning, 2, + "SSL data contains incompatible entries " + "for (%p; %d). Old library is %S but new " + "library is %s.", + key, p->code, prev, p->library); + if (rc < 0) { + goto error; + } continue; } PyObject *value = PyUnicode_FromString(p->library); From 32f7cbaf85aad8f4193622134f6f405c01584db0 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, 5 Jan 2025 16:11:05 +0100 Subject: [PATCH 36/38] clear exception after emitting the warning --- Modules/_ssl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 8aec3b0ad7d309..baae22c12b78f8 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -6927,6 +6927,7 @@ py_ht_errcode_to_name_create(void) { if (rc < 0) { goto error; } + PyErr_Clear(); continue; } PyObject *value = PyUnicode_FromString(p->mnemonic); @@ -6983,6 +6984,7 @@ py_ht_libcode_to_name_create(void) { if (rc < 0) { goto error; } + PyErr_Clear(); continue; } PyObject *value = PyUnicode_FromString(p->library); From a88ec9134593736f6a53b801397da86d82f2512c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:28:24 +0100 Subject: [PATCH 37/38] fix error path when formatting message --- Modules/_ssl.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index baae22c12b78f8..c0c0ef15f39c5e 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -612,6 +612,9 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, + 1 + suffix_len ); res = PyUnicode_New(ressize, 127); + if (res == NULL) { + return NULL; + } rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s: %s (_ssl.c:%d)", lib_cstr, reason_cstr, errstr, verify_cstr, lineno); } @@ -625,6 +628,9 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, + 1 + suffix_len ); res = PyUnicode_New(ressize, 127); + if (res == NULL) { + return NULL; + } rc = snprintf(CHARBUF(res), ressize + 1, "[%s: %s] %s (_ssl.c:%d)", lib_cstr, reason_cstr, errstr, lineno); } @@ -637,6 +643,9 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, + 1 + suffix_len ); res = PyUnicode_New(ressize, 127); + if (res == NULL) { + return NULL; + } rc = snprintf(CHARBUF(res), ressize + 1, "[%s] %s (_ssl.c:%d)", lib_cstr, errstr, lineno); } @@ -647,13 +656,17 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, + 1 + suffix_len ); res = PyUnicode_New(ressize, 127); + if (res == NULL) { + return NULL; + } rc = snprintf(CHARBUF(res), ressize + 1, "%s (_ssl.c:%d)", errstr, lineno); } #undef CHARBUF #undef CSTRBUF if (rc < 0) { - Py_XDECREF(res); + assert(res != NULL); + Py_DECREF(res); /* fallback to slow path if snprintf() failed */ return PyUnicode_FromFormat("%s (_ssl.c:%d)", errstr, lineno); } From 68f8bc7825175b4cced3d696174afdd054e16466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:22:49 +0100 Subject: [PATCH 38/38] include hexadecimal error code value when reporting unknown error --- Modules/_ssl.c | 76 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/Modules/_ssl.c b/Modules/_ssl.c index fed93aeb620888..e03e58cb0904e1 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -570,6 +570,20 @@ ssl_error_lineno_width(int lineno) return 1 + (size_t)log10(lineno); } +/* Dataclass used for constructing SSL errors. */ +typedef struct ssl_error_info { + /* The SSL error number to pass to the exception constructor. */ + int ssl_errno; + /* + * The SSL packed error code. If nonzero and no error message is + * given, this will be used to generate a default error message. + */ + py_ssl_errcode errcode; + + const char *filename; + int lineno; +} ssl_error_info; + /* * Construct a Unicode object containing the formatted SSL error message. * @@ -577,11 +591,8 @@ ssl_error_lineno_width(int lineno) */ static PyObject * format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, - const char *errstr, - const char *filename, int lineno) + const char *errstr, const ssl_error_info info) { - assert(errstr != NULL); - #define CHECK_OBJECT(x) \ do { \ assert(x == NULL || PyUnicode_CheckExact(x)); \ @@ -592,7 +603,20 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, CHECK_OBJECT(verify); #undef CHECK_OBJECT + char tmp[32]; + if (errstr == NULL && info.errcode != 0) { + /* at most 8 hexadecimal digits in info.errcode (unsigned long) */ + if (snprintf(tmp, 32, "unknown error (0x%lx)", info.errcode) != -1) { + errstr = tmp; + } + } + if (errstr == NULL) { + errstr = "unknown error"; + } + + const char *filename = info.filename; const size_t filename_len = ssl_error_filename_width(filename); + int lineno = info.lineno; const size_t lineno_len = ssl_error_lineno_width(lineno); /* exact length of "(_ssl.c:LINENO)" */ const size_t suffix_len = 1 + filename_len + 1 + lineno_len + 1; @@ -601,6 +625,7 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, PyObject *res = NULL; #define CSTRBUF(x) ((const char *)PyUnicode_DATA((x))) #define CHARBUF(x) ((char *)PyUnicode_DATA((x))) + if (lib && reason && verify) { /* [LIB: REASON] ERROR: VERIFY (_ssl.c:LINENO) */ const char *lib_cstr = CSTRBUF(lib); @@ -679,21 +704,18 @@ format_ssl_error_message(PyObject *lib, PyObject *reason, PyObject *verify, * Parameters * * exc_type The SSL exception type. - * ssl_errno The SSL error number to pass to the exception constructor. * lib The ASCII-encoded library obtained from a packed error code. * reason The ASCII-encoded reason obtained from a packed error code. - * errstr The non-NULL error message to use. * * A non-NULL library or reason is stored in the final exception object. */ static PyObject * -build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno, +build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, PyObject *lib, PyObject *reason, const char *errstr, - const char *filename, int lineno) + const ssl_error_info info) { /* build message */ - PyObject *message = format_ssl_error_message(lib, reason, NULL, errstr, - filename, lineno); + PyObject *message = format_ssl_error_message(lib, reason, NULL, errstr, info); if (message == NULL) { return NULL; } @@ -703,6 +725,7 @@ build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno * in the lowest bits of the error code and thus is compatible with * the ERR_GET_REASON() macro. */ + int ssl_errno = info.ssl_errno; assert(ssl_errno == ERR_GET_REASON(ssl_errno)); PyObject *args = Py_BuildValue("iN", ssl_errno, message /* stolen */); if (args == NULL) { @@ -739,9 +762,9 @@ build_ssl_simple_error(_sslmodulestate *state, PyObject *exc_type, int ssl_errno * Same as build_ssl_simple_error() but for SSL verification errors. */ static PyObject * -build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_errno, +build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, PyObject *lib, PyObject *reason, const char *errstr, - const char *filename, int lineno) + const ssl_error_info info) { assert(sslsock != NULL); PyObject *exc = NULL, *exc_dict = NULL, *verify = NULL, *verify_code = NULL; @@ -776,12 +799,13 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err goto fail; } /* build message */ - PyObject *message = format_ssl_error_message(lib, reason, verify, - errstr, filename, lineno); + PyObject *message = format_ssl_error_message(lib, reason, verify, errstr, + info); if (message == NULL) { goto fail; } /* see build_ssl_simple_error() for the assertion's rationale */ + int ssl_errno = info.ssl_errno; assert(ssl_errno == ERR_GET_REASON(ssl_errno)); PyObject *args = Py_BuildValue("iN", ssl_errno, message /* stolen */); if (args == NULL) { @@ -822,7 +846,8 @@ build_ssl_verify_error(_sslmodulestate *state, PySSLSocket *sslsock, int ssl_err static void fill_and_set_sslerror(_sslmodulestate *state, - PySSLSocket *sslsock, PyObject *exc_type, + PySSLSocket *sslsock, /* may be NULL */ + PyObject *exc_type, /* socket exception type */ int ssl_errno /* passed to exc_type.__init__() */, py_ssl_errcode errcode /* for a default message */, const char *errstr /* may be NULL */, @@ -833,22 +858,23 @@ fill_and_set_sslerror(_sslmodulestate *state, ssl_error_fetch_lib_and_reason(state, errcode, &lib, &reason); } if (errstr == NULL && errcode) { + // When ERR_reason_error_string() returns NULL, build_ssl_*_error() + // will use a default error message containing the hexadecimal value + // of the unknown error code. errstr = ERR_reason_error_string(errcode); } - if (errstr == NULL) { - // ERR_reason_error_string() may return NULL - errstr = "unknown error"; - } + const ssl_error_info info = { + .ssl_errno = ssl_errno, + .errcode = errcode, + .filename = filename, + .lineno = lineno + }; PyObject *exc; if (sslsock != NULL && exc_type == state->PySSLCertVerificationErrorObject) { - exc = build_ssl_verify_error(state, sslsock, ssl_errno, - lib, reason, errstr, - filename, lineno); + exc = build_ssl_verify_error(state, sslsock, lib, reason, errstr, info); } else { - exc = build_ssl_simple_error(state, exc_type, ssl_errno, - lib, reason, errstr, - filename, lineno); + exc = build_ssl_simple_error(state, exc_type, lib, reason, errstr, info); } Py_XDECREF(reason); Py_XDECREF(lib);