diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index a9930183f9a400..ff6053cb7e94d9 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -1290,6 +1290,13 @@ SSL sockets also have the following additional methods and attributes: .. versionadded:: 3.5 +.. method:: SSLSocket.group() + + Return the group used for doing key agreement on this connection. If no + connection has been established, returns ``None``. + + .. versionadded:: next + .. method:: SSLSocket.compression() Return the compression algorithm being used as a string, or ``None`` @@ -1647,6 +1654,25 @@ to speed up repeated connections from the same clients. .. versionadded:: 3.6 +.. method:: SSLContext.get_groups(*, include_aliases=False) + + Get a list of groups implemented for key agreement, taking into + account the current TLS :attr:`~SSLContext.minimum_version` and + :attr:`~SSLContext.maximum_version` values. For example:: + + >>> ctx = ssl.create_default_context() + >>> ctx.minimum_version = ssl.TLSVersion.TLSv1_3 + >>> ctx.maximum_version = ssl.TLSVersion.TLSv1_3 + >>> ctx.get_groups() # doctest: +SKIP + ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448', ...] + + By default, this method returns only the preferred IANA names for the + available groups. However, if the ``include_aliases`` parameter is set to + :const:`True` this method will also return any associated aliases such as + the ECDH curve names supported in older versions of OpenSSL. + + .. versionadded:: next + .. method:: SSLContext.set_default_verify_paths() Load a set of default "certification authority" (CA) certificates from @@ -1672,6 +1698,19 @@ to speed up repeated connections from the same clients. TLS 1.3 cipher suites cannot be disabled with :meth:`~SSLContext.set_ciphers`. +.. method:: SSLContext.set_groups(groups) + + Set the groups allowed for key agreement for sockets created with this + context. It should be a string in the `OpenSSL group list format + `_. + + .. note:: + + When connected, the :meth:`SSLSocket.group` method of SSL sockets will + return the group used for key agreement on that connection. + + .. versionadded:: next + .. method:: SSLContext.set_alpn_protocols(protocols) Specify which protocols the socket should advertise during the SSL/TLS diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index a416cbb4cc8eab..7ac4d8587ce7d5 100644 --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -1421,6 +1421,9 @@ is equivalent to :: class Foo(object): pass +There may be one or more base classes; see :ref:`multiple-inheritance` below for more +information. + The class's suite is then executed in a new execution frame (see :ref:`naming`), using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class's @@ -1490,6 +1493,119 @@ can be used to create instance variables with different implementation details. were introduced in :pep:`318`. +.. _multiple-inheritance: + +Multiple inheritance +-------------------- + +Python classes may have multiple base classes, a technique known as +*multiple inheritance*. The base classes are specified in the class definition +by listing them in parentheses after the class name, separated by commas. +For example, the following class definition: + +.. doctest:: + + >>> class A: pass + >>> class B: pass + >>> class C(A, B): pass + +defines a class ``C`` that inherits from classes ``A`` and ``B``. + +The :term:`method resolution order` (MRO) is the order in which base classes are +searched when looking up an attribute on a class. See :ref:`python_2.3_mro` for a +description of how Python determines the MRO for a class. + +Multiple inheritance is not always allowed. Attempting to define a class with multiple +inheritance will raise an error if one of the bases does not allow subclassing, if a consistent MRO +cannot be created, if no valid metaclass can be determined, or if there is an instance +layout conflict. We'll discuss each of these in turn. + +First, all base classes must allow subclassing. While most classes allow subclassing, +some built-in classes do not, such as :class:`bool`: + +.. doctest:: + + >>> class SubBool(bool): # TypeError + ... pass + Traceback (most recent call last): + ... + TypeError: type 'bool' is not an acceptable base type + +In the resolved MRO of a class, the class's bases appear in the order they were +specified in the class's bases list. Additionally, the MRO always lists a child +class before any of its bases. A class definition will fail if it is impossible to +resolve a consistent MRO that satisfies these rules from the list of bases provided: + +.. doctest:: + + >>> class Base: pass + >>> class Child(Base): pass + >>> class Grandchild(Base, Child): pass # TypeError + Traceback (most recent call last): + ... + TypeError: Cannot create a consistent method resolution order (MRO) for bases Base, Child + +In the MRO of ``Grandchild``, ``Base`` must appear before ``Child`` because it is first +in the base class list, but it must also appear after ``Child`` because it is a parent of +``Child``. This is a contradiction, so the class cannot be defined. + +If some of the bases have a custom :term:`metaclass`, the metaclass of the resulting class +is chosen among the metaclasses of the bases and the explicitly specified metaclass of the +child class. It must be a metaclass that is a subclass of +all other candidate metaclasses. If no such metaclass exists among the candidates, +the class cannot be created, as explained in :ref:`metaclass-determination`. + +Finally, the instance layouts of the bases must be compatible. This means that it must be +possible to compute a *solid base* for the class. Exactly which classes are solid bases +depends on the Python implementation. + +.. impl-detail:: + + In CPython, a class is a solid base if it has a + nonempty :attr:`~object.__slots__` definition. + Many but not all classes defined in C are also solid bases, including most + builtins (such as :class:`int` or :class:`BaseException`) + but excluding most concrete :class:`Exception` classes. Generally, a C class + is a solid base if its underlying struct is different in size from its base class. + +Every class has a solid base. :class:`object`, the base class, has itself as its solid base. +If there is a single base, the child class's solid base is that class if it is a solid base, +or else the base class's solid base. If there are multiple bases, we first find the solid base +for each base class to produce a list of candidate solid bases. If there is a unique solid base +that is a subclass of all others, then that class is the solid base. Otherwise, class creation +fails. + +Example: + +.. doctest:: + + >>> class Solid1: + ... __slots__ = ("solid1",) + >>> + >>> class Solid2: + ... __slots__ = ("solid2",) + >>> + >>> class SolidChild(Solid1): + ... __slots__ = ("solid_child",) + >>> + >>> class C1: # solid base is `object` + ... pass + >>> + >>> # OK: solid bases are `Solid1` and `object`, and `Solid1` is a subclass of `object`. + >>> class C2(Solid1, C1): # solid base is `Solid1` + ... pass + >>> + >>> # OK: solid bases are `SolidChild` and `Solid1`, and `SolidChild` is a subclass of `Solid1`. + >>> class C3(SolidChild, Solid1): # solid base is `SolidChild` + ... pass + >>> + >>> # Error: solid bases are `Solid1` and `Solid2`, but neither is a subclass of the other. + >>> class C4(Solid1, Solid2): # error: no single solid base + ... pass + Traceback (most recent call last): + ... + TypeError: multiple bases have instance lay-out conflict + .. _async: Coroutines diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 4a099e81daccb3..7af3457070b84a 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2629,7 +2629,7 @@ Notes on using *__slots__*: * :attr:`~object.__class__` assignment works only if both classes have the same *__slots__*. -* :ref:`Multiple inheritance ` with multiple slotted parent +* :ref:`Multiple inheritance ` with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots (the other bases must have empty slot layouts) - violations raise @@ -2779,6 +2779,8 @@ Resolving MRO entries Core support for typing module and generic types. +.. _metaclass-determination: + Determining the appropriate metaclass ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. index:: diff --git a/Doc/tutorial/classes.rst b/Doc/tutorial/classes.rst index 9d0fab8861d2a9..fa964271d79bd8 100644 --- a/Doc/tutorial/classes.rst +++ b/Doc/tutorial/classes.rst @@ -663,6 +663,9 @@ Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance. For more detail, see :ref:`python_2.3_mro`. +In some cases multiple inheritance is not allowed; see :ref:`multiple-inheritance` +for details. + .. _tut-private: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 1128da875a8847..d89260b32dfb82 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -300,6 +300,24 @@ ssl supports "External PSKs" in TLSv1.3, as described in RFC 9258. (Contributed by Will Childs-Klein in :gh:`133624`.) +* Added new methods for managing groups used for SSL key agreement + + * :meth:`ssl.SSLContext.set_groups` sets the groups allowed for doing + key agreement, extending the previous + :meth:`ssl.SSLContext.set_ecdh_curve` method. + This new API provides the ability to list multiple groups and + supports fixed-field and post-quantum groups in addition to ECDH + curves. This method can also be used to control what key shares + are sent in the TLS handshake. + * :meth:`ssl.SSLSocket.group` returns the group selected for doing key + agreement on the current connection after the TLS handshake completes. + This call requires OpenSSL 3.2 or later. + * :meth:`ssl.SSLContext.get_groups` returns a list of all available key + agreement groups compatible with the minimum and maximum TLS versions + currently set in the context. This call requires OpenSSL 3.5 or later. + + (Contributed by Ron Frederick in :gh:`136306`) + tarfile ------- diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h index 25bb224921aa8b..6ab569393e5ce1 100644 --- a/Include/internal/pycore_dict.h +++ b/Include/internal/pycore_dict.h @@ -113,6 +113,8 @@ extern Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t has extern Py_ssize_t _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr); extern Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr); +extern int _PyDict_GetMethodStackRef(PyDictObject *dict, PyObject *name, _PyStackRef *method); + extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *); extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key); diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 493377b4c25040..5e7dda3a3715a1 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1005,6 +1005,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(imag)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(importlib)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(in_fd)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(include_aliases)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(incoming)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(index)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(indexgroup)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 5dfea2f479d5fe..6908cbf78f349e 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -496,6 +496,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(imag) STRUCT_FOR_ID(importlib) STRUCT_FOR_ID(in_fd) + STRUCT_FOR_ID(include_aliases) STRUCT_FOR_ID(incoming) STRUCT_FOR_ID(index) STRUCT_FOR_ID(indexgroup) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 85ced09d29d338..da2ed7422c9deb 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1003,6 +1003,7 @@ extern "C" { INIT_ID(imag), \ INIT_ID(importlib), \ INIT_ID(in_fd), \ + INIT_ID(include_aliases), \ INIT_ID(incoming), \ INIT_ID(index), \ INIT_ID(indexgroup), \ diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 6bf82d8322f508..c4e8f10fe05276 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -839,6 +839,13 @@ _Py_TryXGetStackRef(PyObject **src, _PyStackRef *out) #endif +#define PyStackRef_XSETREF(dst, src) \ + do { \ + _PyStackRef _tmp_dst_ref = (dst); \ + (dst) = (src); \ + PyStackRef_XCLOSE(_tmp_dst_ref); \ + } while(0) + // Like Py_VISIT but for _PyStackRef fields #define _Py_VISIT_STACKREF(ref) \ do { \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 6018d98d156a65..b1f411945e7856 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1772,6 +1772,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(include_aliases); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(incoming); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/ssl.py b/Lib/ssl.py index 86fb8990636692..5b8762bcdc25d1 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -931,6 +931,10 @@ def cipher(self): ssl_version, secret_bits)``.""" return self._sslobj.cipher() + def group(self): + """Return the currently selected key agreement group name.""" + return self._sslobj.group() + def shared_ciphers(self): """Return a list of ciphers shared by the client during the handshake or None if this is not a valid server connection. @@ -1210,6 +1214,14 @@ def cipher(self): else: return self._sslobj.cipher() + @_sslcopydoc + def group(self): + self._checkClosed() + if self._sslobj is None: + return None + else: + return self._sslobj.group() + @_sslcopydoc def shared_ciphers(self): self._checkClosed() diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 7a7a8c40d47d40..c8939383c75d6d 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -48,6 +48,8 @@ PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) HOST = socket_helper.HOST IS_OPENSSL_3_0_0 = ssl.OPENSSL_VERSION_INFO >= (3, 0, 0) +CAN_GET_SELECTED_OPENSSL_GROUP = ssl.OPENSSL_VERSION_INFO >= (3, 2) +CAN_GET_AVAILABLE_OPENSSL_GROUPS = ssl.OPENSSL_VERSION_INFO >= (3, 5) PY_SSL_DEFAULT_CIPHERS = sysconfig.get_config_var('PY_SSL_DEFAULT_CIPHERS') PROTOCOL_TO_TLS_VERSION = {} @@ -960,6 +962,19 @@ def test_get_ciphers(self): len(intersection), 2, f"\ngot: {sorted(names)}\nexpected: {sorted(expected)}" ) + def test_set_groups(self): + ctx = ssl.create_default_context() + self.assertIsNone(ctx.set_groups('P-256:X25519')) + self.assertRaises(ssl.SSLError, ctx.set_groups, 'P-256:xxx') + + @unittest.skipUnless(CAN_GET_AVAILABLE_OPENSSL_GROUPS, + "OpenSSL version doesn't support getting groups") + def test_get_groups(self): + ctx = ssl.create_default_context() + # By default, only return official IANA names. + self.assertNotIn('P-256', ctx.get_groups()) + self.assertIn('P-256', ctx.get_groups(include_aliases=True)) + def test_options(self): # Test default SSLContext options ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -2720,6 +2735,8 @@ def server_params_test(client_context, server_context, indata=b"FOO\n", 'session_reused': s.session_reused, 'session': s.session, }) + if CAN_GET_SELECTED_OPENSSL_GROUP: + stats.update({'group': s.group()}) s.close() stats['server_alpn_protocols'] = server.selected_alpn_protocols stats['server_shared_ciphers'] = server.shared_ciphers @@ -3870,6 +3887,8 @@ def test_no_shared_ciphers(self): with self.assertRaises(OSError): s.connect((HOST, server.port)) self.assertIn("NO_SHARED_CIPHER", server.conn_errors[0]) + self.assertIsNone(s.cipher()) + self.assertIsNone(s.group()) def test_version_basic(self): """ @@ -4145,6 +4164,38 @@ def test_ecdh_curve(self): chatty=True, connectionchatty=True, sni_name=hostname) + def test_groups(self): + # server secp384r1, client auto + client_context, server_context, hostname = testing_context() + + server_context.set_groups("secp384r1") + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_GROUP: + self.assertEqual(stats['group'], "secp384r1") + + # server auto, client secp384r1 + client_context, server_context, hostname = testing_context() + client_context.set_groups("secp384r1") + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + stats = server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + if CAN_GET_SELECTED_OPENSSL_GROUP: + self.assertEqual(stats['group'], "secp384r1") + + # server / client curve mismatch + client_context, server_context, hostname = testing_context() + client_context.set_groups("prime256v1") + server_context.set_groups("secp384r1") + server_context.minimum_version = ssl.TLSVersion.TLSv1_3 + with self.assertRaises(ssl.SSLError): + server_params_test(client_context, server_context, + chatty=True, connectionchatty=True, + sni_name=hostname) + def test_selected_alpn_protocol(self): # selected_alpn_protocol() is None unless ALPN is used. client_context, server_context, hostname = testing_context() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-25-22-31-52.gh-issue-131338.zJDCMp.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-25-22-31-52.gh-issue-131338.zJDCMp.rst new file mode 100644 index 00000000000000..6c064e8f4a0339 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-25-22-31-52.gh-issue-131338.zJDCMp.rst @@ -0,0 +1,2 @@ +Disable computed stack limit checks on non-glibc linux platforms to fix +crashes on deep recursion. diff --git a/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst b/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst new file mode 100644 index 00000000000000..5556c512681b78 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-04-23-45-00.gh-issue-136306.O1YLIU.rst @@ -0,0 +1 @@ +:mod:`ssl` can now get and set groups used for key agreement. diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 5887d380046b00..ab30258faf3f62 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -2176,6 +2176,33 @@ _ssl__SSLSocket_cipher_impl(PySSLSocket *self) return cipher_to_tuple(current); } +/*[clinic input] +@critical_section +_ssl._SSLSocket.group +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLSocket_group_impl(PySSLSocket *self) +/*[clinic end generated code: output=9c168ee877017b95 input=5f187d8bf0d433b7]*/ +{ +#if OPENSSL_VERSION_NUMBER >= 0x30200000L + const char *group_name; + + if (self->ssl == NULL) { + Py_RETURN_NONE; + } + group_name = SSL_get0_group_name(self->ssl); + if (group_name == NULL) { + Py_RETURN_NONE; + } + return PyUnicode_DecodeFSDefault(group_name); +#else + PyErr_SetString(PyExc_NotImplementedError, + "Getting selected group requires OpenSSL 3.2 or later."); + return NULL; +#endif +} + /*[clinic input] @critical_section _ssl._SSLSocket.version @@ -3240,6 +3267,7 @@ static PyMethodDef PySSLMethods[] = { _SSL__SSLSOCKET_GETPEERCERT_METHODDEF _SSL__SSLSOCKET_GET_CHANNEL_BINDING_METHODDEF _SSL__SSLSOCKET_CIPHER_METHODDEF + _SSL__SSLSOCKET_GROUP_METHODDEF _SSL__SSLSOCKET_SHARED_CIPHERS_METHODDEF _SSL__SSLSOCKET_VERSION_METHODDEF _SSL__SSLSOCKET_SELECTED_ALPN_PROTOCOL_METHODDEF @@ -3622,6 +3650,89 @@ _ssl__SSLContext_get_ciphers_impl(PySSLContext *self) } +/*[clinic input] +@critical_section +_ssl._SSLContext.set_groups + grouplist: str + / +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_set_groups_impl(PySSLContext *self, const char *grouplist) +/*[clinic end generated code: output=0b5d05dfd371ffd0 input=2cc64cef21930741]*/ +{ + if (!SSL_CTX_set1_groups_list(self->ctx, grouplist)) { + _setSSLError(get_state_ctx(self), "unrecognized group", 0, __FILE__, __LINE__); + return NULL; + } + Py_RETURN_NONE; +} + +/*[clinic input] +@critical_section +_ssl._SSLContext.get_groups + * + include_aliases: bool = False +[clinic start generated code]*/ + +static PyObject * +_ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases) +/*[clinic end generated code: output=6d6209dd1051529b input=3e8ee5deb277dcc5]*/ +{ +#if OPENSSL_VERSION_NUMBER >= 0x30500000L + STACK_OF(OPENSSL_CSTRING) *groups = NULL; + const char *group; + int i, num; + PyObject *item, *result = NULL; + + // This "groups" object is dynamically allocated, but the strings inside + // it are internal constants which shouldn't ever be modified or freed. + if ((groups = sk_OPENSSL_CSTRING_new_null()) == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate stack", 0, __FILE__, __LINE__); + goto error; + } + + if (!SSL_CTX_get0_implemented_groups(self->ctx, include_aliases, groups)) { + _setSSLError(get_state_ctx(self), "Can't get groups", 0, __FILE__, __LINE__); + goto error; + } + + num = sk_OPENSSL_CSTRING_num(groups); + result = PyList_New(num); + if (result == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate list", 0, __FILE__, __LINE__); + goto error; + } + + for (i = 0; i < num; ++i) { + // There's no allocation here, so group won't ever be NULL. + group = sk_OPENSSL_CSTRING_value(groups, i); + assert(group != NULL); + + // Group names are plain ASCII, so there's no chance of a decoding + // error here. However, an allocation failure could occur when + // constructing the Unicode version of the names. + item = PyUnicode_DecodeASCII(group, strlen(group), "strict"); + if (item == NULL) { + _setSSLError(get_state_ctx(self), "Can't allocate group name", 0, __FILE__, __LINE__); + goto error; + } + + PyList_SET_ITEM(result, i, item); + } + + sk_OPENSSL_CSTRING_free(groups); + return result; +error: + Py_XDECREF(result); + sk_OPENSSL_CSTRING_free(groups); + return NULL; +#else + PyErr_SetString(PyExc_NotImplementedError, + "Getting implemented groups requires OpenSSL 3.5 or later."); + return NULL; +#endif +} static int do_protocol_selection(int alpn, unsigned char **out, unsigned char *outlen, @@ -5472,6 +5583,7 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT__WRAP_SOCKET_METHODDEF _SSL__SSLCONTEXT__WRAP_BIO_METHODDEF _SSL__SSLCONTEXT_SET_CIPHERS_METHODDEF + _SSL__SSLCONTEXT_SET_GROUPS_METHODDEF _SSL__SSLCONTEXT__SET_ALPN_PROTOCOLS_METHODDEF _SSL__SSLCONTEXT_LOAD_CERT_CHAIN_METHODDEF _SSL__SSLCONTEXT_LOAD_DH_PARAMS_METHODDEF @@ -5482,6 +5594,7 @@ static struct PyMethodDef context_methods[] = { _SSL__SSLCONTEXT_CERT_STORE_STATS_METHODDEF _SSL__SSLCONTEXT_GET_CA_CERTS_METHODDEF _SSL__SSLCONTEXT_GET_CIPHERS_METHODDEF + _SSL__SSLCONTEXT_GET_GROUPS_METHODDEF _SSL__SSLCONTEXT_SET_PSK_CLIENT_CALLBACK_METHODDEF _SSL__SSLCONTEXT_SET_PSK_SERVER_CALLBACK_METHODDEF {NULL, NULL} /* sentinel */ diff --git a/Modules/clinic/_ssl.c.h b/Modules/clinic/_ssl.c.h index 7027d87379283d..5b80fab0abb45e 100644 --- a/Modules/clinic/_ssl.c.h +++ b/Modules/clinic/_ssl.c.h @@ -196,6 +196,29 @@ _ssl__SSLSocket_cipher(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl__SSLSocket_group__doc__, +"group($self, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLSOCKET_GROUP_METHODDEF \ + {"group", (PyCFunction)_ssl__SSLSocket_group, METH_NOARGS, _ssl__SSLSocket_group__doc__}, + +static PyObject * +_ssl__SSLSocket_group_impl(PySSLSocket *self); + +static PyObject * +_ssl__SSLSocket_group(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLSocket_group_impl((PySSLSocket *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(_ssl__SSLSocket_version__doc__, "version($self, /)\n" "--\n" @@ -969,6 +992,111 @@ _ssl__SSLContext_get_ciphers(PyObject *self, PyObject *Py_UNUSED(ignored)) return return_value; } +PyDoc_STRVAR(_ssl__SSLContext_set_groups__doc__, +"set_groups($self, grouplist, /)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_SET_GROUPS_METHODDEF \ + {"set_groups", (PyCFunction)_ssl__SSLContext_set_groups, METH_O, _ssl__SSLContext_set_groups__doc__}, + +static PyObject * +_ssl__SSLContext_set_groups_impl(PySSLContext *self, const char *grouplist); + +static PyObject * +_ssl__SSLContext_set_groups(PyObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + const char *grouplist; + + if (!PyUnicode_Check(arg)) { + _PyArg_BadArgument("set_groups", "argument", "str", arg); + goto exit; + } + Py_ssize_t grouplist_length; + grouplist = PyUnicode_AsUTF8AndSize(arg, &grouplist_length); + if (grouplist == NULL) { + goto exit; + } + if (strlen(grouplist) != (size_t)grouplist_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_set_groups_impl((PySSLContext *)self, grouplist); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + +PyDoc_STRVAR(_ssl__SSLContext_get_groups__doc__, +"get_groups($self, /, *, include_aliases=False)\n" +"--\n" +"\n"); + +#define _SSL__SSLCONTEXT_GET_GROUPS_METHODDEF \ + {"get_groups", _PyCFunction_CAST(_ssl__SSLContext_get_groups), METH_FASTCALL|METH_KEYWORDS, _ssl__SSLContext_get_groups__doc__}, + +static PyObject * +_ssl__SSLContext_get_groups_impl(PySSLContext *self, int include_aliases); + +static PyObject * +_ssl__SSLContext_get_groups(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(include_aliases), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"include_aliases", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "get_groups", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int include_aliases = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_kwonly; + } + include_aliases = PyObject_IsTrue(args[0]); + if (include_aliases < 0) { + goto exit; + } +skip_optional_kwonly: + Py_BEGIN_CRITICAL_SECTION(self); + return_value = _ssl__SSLContext_get_groups_impl((PySSLContext *)self, include_aliases); + Py_END_CRITICAL_SECTION(); + +exit: + return return_value; +} + PyDoc_STRVAR(_ssl__SSLContext__set_alpn_protocols__doc__, "_set_alpn_protocols($self, protos, /)\n" "--\n" @@ -3014,4 +3142,4 @@ _ssl_enum_crls(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef _SSL_ENUM_CRLS_METHODDEF #define _SSL_ENUM_CRLS_METHODDEF #endif /* !defined(_SSL_ENUM_CRLS_METHODDEF) */ -/*[clinic end generated code: output=1adc3780d8ca682a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c409bdf3c123b28b input=a9049054013a1b77]*/ diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 200b2b8c7d8310..7fd929af5f27b4 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -18,6 +18,10 @@ */ #ifdef AF_BTH # include +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpragma-pack" +# endif # include /* @@ -51,7 +55,10 @@ struct SOCKADDR_BTH_REDEF { }; # include -#endif +# ifdef __clang__ +# pragma clang diagnostic pop +# endif +#endif /* AF_BTH */ /* Windows 'supports' CMSG_LEN, but does not follow the POSIX standard * interface at all, so there is no point including the code that diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 0ed52ac5e87b6e..928b905aaedb1b 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -1581,32 +1581,47 @@ _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyOb return ix; } +static Py_ssize_t +lookup_threadsafe_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) +{ + assert(dk->dk_kind == DICT_KEYS_UNICODE); + assert(PyUnicode_CheckExact(key)); + + Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); + if (ix == DKIX_EMPTY) { + *value_addr = PyStackRef_NULL; + return ix; + } + else if (ix >= 0) { + PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value; + PyObject *value = _Py_atomic_load_ptr(addr_of_value); + if (value == NULL) { + *value_addr = PyStackRef_NULL; + return DKIX_EMPTY; + } + if (_PyObject_HasDeferredRefcount(value)) { + *value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED }; + return ix; + } + if (_Py_TryIncrefCompare(addr_of_value, value)) { + *value_addr = PyStackRef_FromPyObjectSteal(value); + return ix; + } + return DKIX_KEY_CHANGED; + } + assert(ix == DKIX_KEY_CHANGED); + return ix; +} + Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr) { PyDictKeysObject *dk = _Py_atomic_load_ptr(&mp->ma_keys); if (dk->dk_kind == DICT_KEYS_UNICODE && PyUnicode_CheckExact(key)) { - Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash); - if (ix == DKIX_EMPTY) { - *value_addr = PyStackRef_NULL; + Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, value_addr); + if (ix != DKIX_KEY_CHANGED) { return ix; } - else if (ix >= 0) { - PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value; - PyObject *value = _Py_atomic_load_ptr(addr_of_value); - if (value == NULL) { - *value_addr = PyStackRef_NULL; - return DKIX_EMPTY; - } - if (_PyObject_HasDeferredRefcount(value)) { - *value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED }; - return ix; - } - if (_Py_TryIncrefCompare(addr_of_value, value)) { - *value_addr = PyStackRef_FromPyObjectSteal(value); - return ix; - } - } } PyObject *obj; @@ -1646,6 +1661,46 @@ _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t h #endif +// Looks up the unicode key `key` in the dictionary. Note that `*method` may +// already contain a valid value! See _PyObject_GetMethodStackRef(). +int +_PyDict_GetMethodStackRef(PyDictObject *mp, PyObject *key, _PyStackRef *method) +{ + assert(PyUnicode_CheckExact(key)); + Py_hash_t hash = hash_unicode_key(key); + +#ifdef Py_GIL_DISABLED + PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys); + if (dk->dk_kind == DICT_KEYS_UNICODE) { + _PyStackRef ref; + Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, &ref); + if (ix >= 0) { + assert(!PyStackRef_IsNull(ref)); + PyStackRef_XSETREF(*method, ref); + return 1; + } + else if (ix == DKIX_EMPTY) { + return 0; + } + assert(ix == DKIX_KEY_CHANGED); + } +#endif + + PyObject *obj; + Py_INCREF(mp); + Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj); + Py_DECREF(mp); + if (ix == DKIX_ERROR) { + PyStackRef_CLEAR(*method); + return -1; + } + else if (ix >= 0 && obj != NULL) { + PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(obj)); + return 1; + } + return 0; // not found +} + int _PyDict_HasOnlyStringKeys(PyObject *dict) { diff --git a/Objects/object.c b/Objects/object.c index 3ed7d55593dffa..479f4176a46039 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1753,20 +1753,15 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj, } } if (dict != NULL) { - // TODO: use _Py_dict_lookup_threadsafe_stackref - Py_INCREF(dict); - PyObject *value; - if (PyDict_GetItemRef(dict, name, &value) != 0) { - // found or error - Py_DECREF(dict); - PyStackRef_CLEAR(*method); - if (value != NULL) { - *method = PyStackRef_FromPyObjectSteal(value); - } + assert(PyUnicode_CheckExact(name)); + int found = _PyDict_GetMethodStackRef((PyDictObject *)dict, name, method); + if (found < 0) { + assert(PyStackRef_IsNull(*method)); + return -1; + } + else if (found) { return 0; } - // not found - Py_DECREF(dict); } if (meth_found) { diff --git a/PC/winreg.c b/PC/winreg.c index d1a1c3d1c97850..05a33006c32326 100644 --- a/PC/winreg.c +++ b/PC/winreg.c @@ -49,7 +49,9 @@ PyDoc_STRVAR(module_doc, "ConnectRegistry() - Establishes a connection to a predefined registry handle\n" " on another computer.\n" "CreateKey() - Creates the specified key, or opens it if it already exists.\n" +"CreateKeyEx() - Creates the specified key, or opens it if it already exists.\n" "DeleteKey() - Deletes the specified key.\n" +"DeleteKeyEx() - Deletes the specified key.\n" "DeleteValue() - Removes a named value from the specified registry key.\n" "EnumKey() - Enumerates subkeys of the specified open registry key.\n" "EnumValue() - Enumerates values of the specified open registry key.\n" @@ -69,6 +71,9 @@ PyDoc_STRVAR(module_doc, "SaveKey() - Saves the specified key, and all its subkeys a file.\n" "SetValue() - Associates a value with a specified key.\n" "SetValueEx() - Stores data in the value field of an open registry key.\n" +"DisableReflectionKey() - Disables registry reflection for 32bit processes running on a 64bit OS.\n" +"EnableReflectionKey() - Restores registry reflection for a key.\n" +"QueryReflectionKey() - Determines the reflection state for a key.\n" "\n" "Special objects:\n" "\n" diff --git a/Python/ceval.c b/Python/ceval.c index 291e753dec0ce5..9ccd42bdf0a55c 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -452,7 +452,11 @@ _Py_InitializeRecursionLimits(PyThreadState *tstate) _tstate->c_stack_soft_limit = _tstate->c_stack_hard_limit + _PyOS_STACK_MARGIN_BYTES; #else uintptr_t here_addr = _Py_get_machine_stack_pointer(); -# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && !defined(__NetBSD__) +/// XXX musl supports HAVE_PTHRED_GETATTR_NP, but the resulting stack size +/// (on alpine at least) is much smaller than expected and imposes undue limits +/// compared to the old stack size estimation. (We assume musl is not glibc.) +# if defined(HAVE_PTHREAD_GETATTR_NP) && !defined(_AIX) && \ + !defined(__NetBSD__) && (defined(__GLIBC__) || !defined(__linux__)) size_t stack_size, guard_size; void *stack_addr; pthread_attr_t attr;