diff --git a/.gitignore b/.gitignore index e927c68..bb6f776 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/Python-3.14.4.tar.xz +SOURCES/Python-3.14.5.tar.xz diff --git a/.python3.14.metadata b/.python3.14.metadata index ae8f0f6..9e605ce 100644 --- a/.python3.14.metadata +++ b/.python3.14.metadata @@ -1 +1 @@ -567ffd2b4116db4cbcd3fb7adb304ec32687f99f SOURCES/Python-3.14.4.tar.xz +550bd85f05ba3a75d710e716100db174f13601f6 SOURCES/Python-3.14.5.tar.xz diff --git a/SOURCES/00466-downstream-only-skip-tests-not-working-with-older-expat-version.patch b/SOURCES/00466-downstream-only-skip-tests-not-working-with-older-expat-version.patch index ce1c39d..3897b71 100644 --- a/SOURCES/00466-downstream-only-skip-tests-not-working-with-older-expat-version.patch +++ b/SOURCES/00466-downstream-only-skip-tests-not-working-with-older-expat-version.patch @@ -41,7 +41,7 @@ index 5c10bcedc6..1fd7a273b5 100644 result = BytesIO() xmlgen = XMLGenerator(result) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py -index 0b343cc4bb..145ecacd21 100644 +index 1bd0fde844..f87134c11e 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1573,9 +1573,13 @@ def test_simple_xml(self, chunk_size=None, flush=False): diff --git a/SOURCES/00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch b/SOURCES/00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch index 5fd303c..e8f69ae 100644 --- a/SOURCES/00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch +++ b/SOURCES/00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch @@ -81,10 +81,10 @@ index 2a17c891dd..64017c666c 100644 } #endif diff --git a/Makefile.pre.in b/Makefile.pre.in -index 80a1b590c2..f28f562930 100644 +index da6d7c3315..92a6825b5e 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -3415,3 +3415,6 @@ MODULE__MULTIBYTECODEC_DEPS=$(srcdir)/Modules/cjkcodecs/multibytecodec.h +@@ -3420,3 +3420,6 @@ MODULE__MULTIBYTECODEC_DEPS=$(srcdir)/Modules/cjkcodecs/multibytecodec.h # Local Variables: # mode: makefile # End: diff --git a/SOURCES/00479-cve-2026-1502.patch b/SOURCES/00479-cve-2026-1502.patch deleted file mode 100644 index c820171..0000000 --- a/SOURCES/00479-cve-2026-1502.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Seth Larson -Date: Fri, 10 Apr 2026 10:21:42 -0500 -Subject: 00479: CVE-2026-1502 - -Reject CR/LF in HTTP tunnel request headers - -Co-authored-by: Illia Volochii ---- - Lib/http/client.py | 11 ++++- - Lib/test/test_httplib.py | 45 +++++++++++++++++++ - ...-03-20-09-29-42.gh-issue-146211.PQVbs7.rst | 2 + - 3 files changed, 57 insertions(+), 1 deletion(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst - -diff --git a/Lib/http/client.py b/Lib/http/client.py -index 77f8d26291..6fb7d254ea 100644 ---- a/Lib/http/client.py -+++ b/Lib/http/client.py -@@ -972,13 +972,22 @@ def _wrap_ipv6(self, ip): - return ip - - def _tunnel(self): -+ if _contains_disallowed_url_pchar_re.search(self._tunnel_host): -+ raise ValueError('Tunnel host can\'t contain control characters %r' -+ % (self._tunnel_host,)) - connect = b"CONNECT %s:%d %s\r\n" % ( - self._wrap_ipv6(self._tunnel_host.encode("idna")), - self._tunnel_port, - self._http_vsn_str.encode("ascii")) - headers = [connect] - for header, value in self._tunnel_headers.items(): -- headers.append(f"{header}: {value}\r\n".encode("latin-1")) -+ header_bytes = header.encode("latin-1") -+ value_bytes = value.encode("latin-1") -+ if not _is_legal_header_name(header_bytes): -+ raise ValueError('Invalid header name %r' % (header_bytes,)) -+ if _is_illegal_header_value(value_bytes): -+ raise ValueError('Invalid header value %r' % (value_bytes,)) -+ headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes)) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of -diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py -index bcb828edec..6f3eac6b98 100644 ---- a/Lib/test/test_httplib.py -+++ b/Lib/test/test_httplib.py -@@ -369,6 +369,51 @@ def test_invalid_headers(self): - with self.assertRaisesRegex(ValueError, 'Invalid header'): - conn.putheader(name, value) - -+ def test_invalid_tunnel_headers(self): -+ cases = ( -+ ('Invalid\r\nName', 'ValidValue'), -+ ('Invalid\rName', 'ValidValue'), -+ ('Invalid\nName', 'ValidValue'), -+ ('\r\nInvalidName', 'ValidValue'), -+ ('\rInvalidName', 'ValidValue'), -+ ('\nInvalidName', 'ValidValue'), -+ (' InvalidName', 'ValidValue'), -+ ('\tInvalidName', 'ValidValue'), -+ ('Invalid:Name', 'ValidValue'), -+ (':InvalidName', 'ValidValue'), -+ ('ValidName', 'Invalid\r\nValue'), -+ ('ValidName', 'Invalid\rValue'), -+ ('ValidName', 'Invalid\nValue'), -+ ('ValidName', 'InvalidValue\r\n'), -+ ('ValidName', 'InvalidValue\r'), -+ ('ValidName', 'InvalidValue\n'), -+ ) -+ for name, value in cases: -+ with self.subTest((name, value)): -+ conn = client.HTTPConnection('example.com') -+ conn.set_tunnel('tunnel', headers={ -+ name: value -+ }) -+ conn.sock = FakeSocket('') -+ with self.assertRaisesRegex(ValueError, 'Invalid header'): -+ conn._tunnel() # Called in .connect() -+ -+ def test_invalid_tunnel_host(self): -+ cases = ( -+ 'invalid\r.host', -+ '\ninvalid.host', -+ 'invalid.host\r\n', -+ 'invalid.host\x00', -+ 'invalid host', -+ ) -+ for tunnel_host in cases: -+ with self.subTest(tunnel_host): -+ conn = client.HTTPConnection('example.com') -+ conn.set_tunnel(tunnel_host) -+ conn.sock = FakeSocket('') -+ with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'): -+ conn._tunnel() # Called in .connect() -+ - def test_headers_debuglevel(self): - body = ( - b'HTTP/1.1 200 OK\r\n' -diff --git a/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst -new file mode 100644 -index 0000000000..4993633b8e ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst -@@ -0,0 +1,2 @@ -+Reject CR/LF characters in tunnel request headers for the -+HTTPConnection.set_tunnel() method. diff --git a/SOURCES/00480-cve-2026-4786.patch b/SOURCES/00480-cve-2026-4786.patch deleted file mode 100644 index 1b51d5a..0000000 --- a/SOURCES/00480-cve-2026-4786.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych -Date: Mon, 13 Apr 2026 20:02:52 +0100 -Subject: 00480: CVE-2026-4786 - -Fix webbrowser `%action` substitution bypass of dash-prefix check ---- - Lib/test/test_webbrowser.py | 9 +++++++++ - Lib/webbrowser.py | 5 +++-- - .../2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst | 2 ++ - 3 files changed, 14 insertions(+), 2 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst - -diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py -index 404b3a31a5..bfbcf112b0 100644 ---- a/Lib/test/test_webbrowser.py -+++ b/Lib/test/test_webbrowser.py -@@ -119,6 +119,15 @@ def test_open_bad_new_parameter(self): - arguments=[URL], - kw=dict(new=999)) - -+ def test_reject_action_dash_prefixes(self): -+ browser = self.browser_class(name=CMD_NAME) -+ with self.assertRaises(ValueError): -+ browser.open('%action--incognito') -+ # new=1: action is "--new-window", so "%action" itself expands to -+ # a dash-prefixed flag even with no dash in the original URL. -+ with self.assertRaises(ValueError): -+ browser.open('%action', new=1) -+ - - class EdgeCommandTest(CommandTestMixin, unittest.TestCase): - -diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py -index 0e0b5034e5..97aad6eea5 100644 ---- a/Lib/webbrowser.py -+++ b/Lib/webbrowser.py -@@ -274,7 +274,6 @@ def _invoke(self, args, remote, autoraise, url=None): - - def open(self, url, new=0, autoraise=True): - sys.audit("webbrowser.open", url) -- self._check_url(url) - if new == 0: - action = self.remote_action - elif new == 1: -@@ -288,7 +287,9 @@ def open(self, url, new=0, autoraise=True): - raise Error("Bad 'new' parameter to open(); " - f"expected 0, 1, or 2, got {new}") - -- args = [arg.replace("%s", url).replace("%action", action) -+ self._check_url(url.replace("%action", action)) -+ -+ args = [arg.replace("%action", action).replace("%s", url) - for arg in self.remote_args] - args = [arg for arg in args if arg] - success = self._invoke(args, True, autoraise, url) -diff --git a/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst -new file mode 100644 -index 0000000000..45cdeebe1b ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst -@@ -0,0 +1,2 @@ -+A bypass in :mod:`webbrowser` allowed URLs prefixed with ``%action`` to pass -+the dash-prefix safety check. diff --git a/SOURCES/00481-cve-2026-5713.patch b/SOURCES/00481-cve-2026-5713.patch deleted file mode 100644 index d5b0551..0000000 --- a/SOURCES/00481-cve-2026-5713.patch +++ /dev/null @@ -1,587 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Pablo Galindo Salgado -Date: Mon, 13 Apr 2026 23:22:23 +0100 -Subject: 00481: CVE-2026-5713 - -Validate remote debug offset tables on load ---- - ...-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst | 2 + - Modules/_remote_debugging_module.c | 509 +++++++++++++++++- - 2 files changed, 505 insertions(+), 6 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst - -diff --git a/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst b/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst -new file mode 100644 -index 0000000000..ed138a54a8 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-04-06-13-55-00.gh-issue-148178.Rs7kLm.rst -@@ -0,0 +1,2 @@ -+Hardened :mod:`!_remote_debugging` by validating remote debug offset tables -+before using them to size memory reads or interpret remote layouts. -diff --git a/Modules/_remote_debugging_module.c b/Modules/_remote_debugging_module.c -index a327772258..d756ac326f 100644 ---- a/Modules/_remote_debugging_module.c -+++ b/Modules/_remote_debugging_module.c -@@ -20,6 +20,7 @@ - #include // FRAME_OWNED_BY_CSTACK - #include // struct llist_node - #include // Py_TAG_BITS -+#include // _PyThreadStateImpl - #include "../Python/remote_debug.h" - - // gh-141784: Python.h header must be included first, before system headers. -@@ -41,9 +42,11 @@ - * TYPE DEFINITIONS AND STRUCTURES - * ============================================================================ */ - --#define GET_MEMBER(type, obj, offset) (*(type*)((char*)(obj) + (offset))) -+#define GET_MEMBER(type, obj, offset) \ -+ (*(type *)memcpy(&(type){0}, (const char *)(obj) + (offset), sizeof(type))) - #define CLEAR_PTR_TAG(ptr) (((uintptr_t)(ptr) & ~Py_TAG_BITS)) --#define GET_MEMBER_NO_TAG(type, obj, offset) (type)(CLEAR_PTR_TAG(*(type*)((char*)(obj) + (offset)))) -+#define GET_MEMBER_NO_TAG(type, obj, offset) \ -+ (type)(CLEAR_PTR_TAG(GET_MEMBER(type, obj, offset))) - - /* Size macros for opaque buffers */ - #define SIZEOF_BYTES_OBJ sizeof(PyBytesObject) -@@ -107,6 +110,486 @@ struct _Py_AsyncioModuleDebugOffsets { - } asyncio_thread_state; - }; - -+/* Treat the remote debug tables as untrusted input and validate every -+ * size/offset we later dereference against a fixed local buffer or object -+ * layout before the unwinder starts using them. */ -+#define FIELD_SIZE(type, member) sizeof(((type *)0)->member) -+#define PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS (-2) -+ -+static inline int -+validate_section_size(const char *section_name, uint64_t size) -+{ -+ if (size == 0) { -+ PyErr_Format( -+ PyExc_RuntimeError, -+ "Invalid debug offsets: %s.size must be greater than zero", -+ section_name); -+ return -1; -+ } -+ return 0; -+} -+ -+static inline int -+validate_read_size(const char *section_name, uint64_t size, size_t buffer_size) -+{ -+ if (validate_section_size(section_name, size) < 0) { -+ return -1; -+ } -+ if (size > buffer_size) { -+ PyErr_Format( -+ PyExc_RuntimeError, -+ "Invalid debug offsets: %s.size=%llu exceeds local buffer size %zu", -+ section_name, -+ (unsigned long long)size, -+ buffer_size); -+ return -1; -+ } -+ return 0; -+} -+ -+static inline int -+validate_span( -+ const char *field_name, -+ uint64_t offset, -+ size_t width, -+ uint64_t limit, -+ const char *limit_name) -+{ -+ uint64_t span = (uint64_t)width; -+ if (span > limit || offset > limit - span) { -+ PyErr_Format( -+ PyExc_RuntimeError, -+ "Invalid debug offsets: %s=%llu with width %zu exceeds %s %llu", -+ field_name, -+ (unsigned long long)offset, -+ width, -+ limit_name, -+ (unsigned long long)limit); -+ return -1; -+ } -+ return 0; -+} -+ -+static inline int -+validate_alignment( -+ const char *field_name, -+ uint64_t offset, -+ size_t alignment) -+{ -+ if (alignment > 1 && offset % alignment != 0) { -+ PyErr_Format( -+ PyExc_RuntimeError, -+ "Invalid debug offsets: %s=%llu is not aligned to %zu bytes", -+ field_name, -+ (unsigned long long)offset, -+ alignment); -+ return -1; -+ } -+ return 0; -+} -+ -+static inline int -+validate_field( -+ const char *field_name, -+ uint64_t offset, -+ uint64_t reported_size, -+ size_t width, -+ size_t alignment, -+ size_t buffer_size) -+{ -+ if (validate_alignment(field_name, offset, alignment) < 0) { -+ return -1; -+ } -+ if (validate_span(field_name, offset, width, reported_size, "reported size") < 0) { -+ return -1; -+ } -+ return validate_span(field_name, offset, width, buffer_size, "local buffer size"); -+} -+ -+static inline int -+validate_fixed_field( -+ const char *field_name, -+ uint64_t offset, -+ size_t width, -+ size_t alignment, -+ size_t buffer_size) -+{ -+ if (validate_alignment(field_name, offset, alignment) < 0) { -+ return -1; -+ } -+ return validate_span(field_name, offset, width, buffer_size, "local buffer size"); -+} -+ -+#define PY_REMOTE_DEBUG_VALIDATE_SECTION(section) \ -+ do { \ -+ if (validate_section_size(#section, debug_offsets->section.size) < 0) { \ -+ return -1; \ -+ } \ -+ } while (0) -+ -+#define PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(section, buffer_size) \ -+ do { \ -+ if (validate_read_size(#section, debug_offsets->section.size, buffer_size) < 0) { \ -+ return -1; \ -+ } \ -+ } while (0) -+ -+#define PY_REMOTE_DEBUG_VALIDATE_FIELD(section, field, field_size, field_alignment, buffer_size) \ -+ do { \ -+ if (validate_field( \ -+ #section "." #field, \ -+ debug_offsets->section.field, \ -+ debug_offsets->section.size, \ -+ field_size, \ -+ field_alignment, \ -+ buffer_size) < 0) { \ -+ return -1; \ -+ } \ -+ } while (0) -+ -+#define PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD(section, field, field_size, field_alignment, buffer_size) \ -+ do { \ -+ if (validate_fixed_field( \ -+ #section "." #field, \ -+ debug_offsets->section.field, \ -+ field_size, \ -+ field_alignment, \ -+ buffer_size) < 0) { \ -+ return -1; \ -+ } \ -+ } while (0) -+ -+static inline int -+validate_debug_offsets_layout(struct _Py_DebugOffsets *debug_offsets) -+{ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(runtime_state); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ runtime_state, -+ interpreters_head, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ sizeof(_PyRuntimeState)); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_state); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ threads_head, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ INTERP_STATE_BUFFER_SIZE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ threads_main, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ INTERP_STATE_BUFFER_SIZE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ gil_runtime_state_locked, -+ sizeof(int), -+ _Alignof(int), -+ INTERP_STATE_BUFFER_SIZE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ gil_runtime_state_holder, -+ sizeof(PyThreadState *), -+ _Alignof(PyThreadState *), -+ INTERP_STATE_BUFFER_SIZE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ code_object_generation, -+ sizeof(uint64_t), -+ _Alignof(uint64_t), -+ INTERP_STATE_BUFFER_SIZE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_state, -+ tlbc_generation, -+ sizeof(uint32_t), -+ _Alignof(uint32_t), -+ INTERP_STATE_BUFFER_SIZE); -+ -+ PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(thread_state, SIZEOF_THREAD_STATE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ thread_state, -+ next, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_THREAD_STATE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ thread_state, -+ current_frame, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_THREAD_STATE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ thread_state, -+ native_thread_id, -+ sizeof(unsigned long), -+ _Alignof(unsigned long), -+ SIZEOF_THREAD_STATE); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ thread_state, -+ datastack_chunk, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_THREAD_STATE); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(interpreter_frame); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ previous, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_INTERP_FRAME); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ executable, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_INTERP_FRAME); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ instr_ptr, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_INTERP_FRAME); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ owner, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_INTERP_FRAME); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ stackpointer, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_INTERP_FRAME); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ interpreter_frame, -+ tlbc_index, -+ sizeof(int32_t), -+ _Alignof(int32_t), -+ SIZEOF_INTERP_FRAME); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(code_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ qualname, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_CODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ filename, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_CODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ linetable, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_CODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ firstlineno, -+ sizeof(int), -+ _Alignof(int), -+ SIZEOF_CODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ co_code_adaptive, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_CODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ code_object, -+ co_tlbc, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_CODE_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(pyobject); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ pyobject, -+ ob_type, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_PYOBJECT); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(type_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ type_object, -+ tp_flags, -+ sizeof(unsigned long), -+ _Alignof(unsigned long), -+ SIZEOF_TYPE_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(set_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ set_object, -+ used, -+ sizeof(Py_ssize_t), -+ _Alignof(Py_ssize_t), -+ SIZEOF_SET_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ set_object, -+ mask, -+ sizeof(Py_ssize_t), -+ _Alignof(Py_ssize_t), -+ SIZEOF_SET_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ set_object, -+ table, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_SET_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(long_object, SIZEOF_LONG_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ long_object, -+ lv_tag, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_LONG_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ long_object, -+ ob_digit, -+ sizeof(digit), -+ _Alignof(digit), -+ SIZEOF_LONG_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(bytes_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ bytes_object, -+ ob_size, -+ sizeof(Py_ssize_t), -+ _Alignof(Py_ssize_t), -+ SIZEOF_BYTES_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ bytes_object, -+ ob_sval, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_BYTES_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(unicode_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ unicode_object, -+ length, -+ sizeof(Py_ssize_t), -+ _Alignof(Py_ssize_t), -+ SIZEOF_UNICODE_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ unicode_object, -+ asciiobject_size, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_UNICODE_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(gen_object); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ gen_object, -+ gi_frame_state, -+ sizeof(int8_t), -+ _Alignof(int8_t), -+ SIZEOF_GEN_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ gen_object, -+ gi_iframe, -+ FIELD_SIZE(PyGenObject, gi_iframe), -+ _Alignof(_PyInterpreterFrame), -+ SIZEOF_GEN_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD( -+ llist_node, -+ next, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_LLIST_NODE); -+ -+ return 0; -+} -+ -+static inline int -+validate_async_debug_offsets_layout(struct _Py_AsyncioModuleDebugOffsets *debug_offsets) -+{ -+ PY_REMOTE_DEBUG_VALIDATE_READ_SECTION(asyncio_task_object, SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_name, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_awaited_by, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_is_task, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_awaited_by_is_set, -+ sizeof(char), -+ _Alignof(char), -+ SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_coro, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ SIZEOF_TASK_OBJ); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_task_object, -+ task_node, -+ SIZEOF_LLIST_NODE, -+ _Alignof(struct llist_node), -+ SIZEOF_TASK_OBJ); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_interpreter_state); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_interpreter_state, -+ asyncio_tasks_head, -+ SIZEOF_LLIST_NODE, -+ _Alignof(struct llist_node), -+ sizeof(PyInterpreterState)); -+ -+ PY_REMOTE_DEBUG_VALIDATE_SECTION(asyncio_thread_state); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_thread_state, -+ asyncio_running_loop, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ sizeof(_PyThreadStateImpl)); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_thread_state, -+ asyncio_running_task, -+ sizeof(uintptr_t), -+ _Alignof(uintptr_t), -+ sizeof(_PyThreadStateImpl)); -+ PY_REMOTE_DEBUG_VALIDATE_FIELD( -+ asyncio_thread_state, -+ asyncio_tasks_head, -+ SIZEOF_LLIST_NODE, -+ _Alignof(struct llist_node), -+ sizeof(_PyThreadStateImpl)); -+ -+ return 0; -+} -+ -+#undef PY_REMOTE_DEBUG_VALIDATE_SECTION -+#undef PY_REMOTE_DEBUG_VALIDATE_READ_SECTION -+#undef PY_REMOTE_DEBUG_VALIDATE_FIELD -+#undef PY_REMOTE_DEBUG_VALIDATE_FIXED_FIELD -+#undef FIELD_SIZE -+ - /* ============================================================================ - * STRUCTSEQ TYPE DEFINITIONS - * ============================================================================ */ -@@ -434,7 +917,7 @@ validate_debug_offsets(struct _Py_DebugOffsets *debug_offsets) - return -1; - } - -- return 0; -+ return validate_debug_offsets_layout(debug_offsets); - } - - // Generic function to iterate through all threads -@@ -877,8 +1360,13 @@ read_async_debug( - int result = _Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, async_debug_addr, size, &unwinder->async_debug_offsets); - if (result < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read AsyncioDebug offsets"); -+ return result; - } -- return result; -+ if (validate_async_debug_offsets_layout(&unwinder->async_debug_offsets) < 0) { -+ set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid AsyncioDebug offsets"); -+ return PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS; -+ } -+ return 0; - } - - /* ============================================================================ -@@ -2054,10 +2542,15 @@ static void * - find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr) - { - for (size_t i = 0; i < chunks->count; ++i) { -+ if (chunks->chunks[i].size <= offsetof(_PyStackChunk, data)) { -+ continue; -+ } - uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data); - size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data); - -- if (remote_ptr >= base && remote_ptr < base + payload) { -+ if (payload >= SIZEOF_INTERP_FRAME && -+ remote_ptr >= base && -+ remote_ptr <= base + payload - SIZEOF_INTERP_FRAME) { - return (char *)chunks->chunks[i].local_copy + (remote_ptr - chunks->chunks[i].remote_addr); - } - } -@@ -2624,7 +3117,11 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self, - - // Try to read async debug offsets, but don't fail if they're not available - self->async_debug_offsets_available = 1; -- if (read_async_debug(self) < 0) { -+ int async_debug_result = read_async_debug(self); -+ if (async_debug_result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) { -+ return -1; -+ } -+ if (async_debug_result < 0) { - PyErr_Clear(); - memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets)); - self->async_debug_offsets_available = 0; diff --git a/SOURCES/00482-cve-2026-6100.patch b/SOURCES/00482-cve-2026-6100.patch deleted file mode 100644 index 0c42470..0000000 --- a/SOURCES/00482-cve-2026-6100.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Mon, 13 Apr 2026 03:40:54 +0200 -Subject: 00482: CVE-2026-6100 - -Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor - -Co-authored-by: Stan Ulbrych ---- - .../Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst | 5 +++++ - Modules/_bz2module.c | 1 + - Modules/_lzmamodule.c | 1 + - Modules/zlibmodule.c | 1 + - 4 files changed, 8 insertions(+) - create mode 100644 Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst - -diff --git a/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst -new file mode 100644 -index 0000000000..9502189ab1 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst -@@ -0,0 +1,5 @@ -+Fix a dangling input pointer in :class:`lzma.LZMADecompressor`, -+:class:`bz2.BZ2Decompressor`, and internal :class:`!zlib._ZlibDecompressor` -+when memory allocation fails with :exc:`MemoryError`, which could let a -+subsequent :meth:`!decompress` call read or write through a stale pointer to -+the already-released caller buffer. -diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c -index 9e85e0de42..055ce82e7d 100644 ---- a/Modules/_bz2module.c -+++ b/Modules/_bz2module.c -@@ -593,6 +593,7 @@ decompress(BZ2Decompressor *d, char *data, size_t len, Py_ssize_t max_length) - return result; - - error: -+ bzs->next_in = NULL; - Py_XDECREF(result); - return NULL; - } -diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c -index 462c2181fa..6785dc5673 100644 ---- a/Modules/_lzmamodule.c -+++ b/Modules/_lzmamodule.c -@@ -1120,6 +1120,7 @@ decompress(Decompressor *d, uint8_t *data, size_t len, Py_ssize_t max_length) - return result; - - error: -+ lzs->next_in = NULL; - Py_XDECREF(result); - return NULL; - } -diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c -index 5b6b0c5cac..a86aa5fdbb 100644 ---- a/Modules/zlibmodule.c -+++ b/Modules/zlibmodule.c -@@ -1675,6 +1675,7 @@ decompress(ZlibDecompressor *self, uint8_t *data, - return result; - - error: -+ self->zst.next_in = NULL; - Py_XDECREF(result); - return NULL; - } diff --git a/SOURCES/00487-fixup-for-CVE-2026-6019.patch b/SOURCES/00487-fixup-for-CVE-2026-6019.patch new file mode 100644 index 0000000..6390ec9 --- /dev/null +++ b/SOURCES/00487-fixup-for-CVE-2026-6019.patch @@ -0,0 +1,124 @@ +From 4f17f6e04534d93fbd23862f9f290d3a281ab30b Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Fri, 5 Jun 2026 10:50:14 +0200 +Subject: [PATCH] [3.14] gh-149144: Use decodeURIComponent() for UTF-8 support + in js_output() (GH-149157) + +(cherry picked from commit 461b1d96313de02992d284c1782be9aff24586c9) + +Co-authored-by: Seth Larson +--- + Lib/http/cookies.py | 6 +++--- + Lib/test/test_http_cookies.py | 27 ++++++++++++++------------- + 2 files changed, 17 insertions(+), 16 deletions(-) + +diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py +index 5c5b14788dc2f09..abebb4b69fd05f4 100644 +--- a/Lib/http/cookies.py ++++ b/Lib/http/cookies.py +@@ -391,18 +391,18 @@ def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self.OutputString()) + + def js_output(self, attrs=None): +- import base64 ++ import urllib.parse + # Print javascript + output_string = self.OutputString(attrs) + if _has_control_character(output_string): + raise CookieError("Control characters are not allowed in cookies") + # Base64-encode value to avoid template + # injection in cookie values. +- output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii") ++ output_encoded = urllib.parse.quote(output_string, safe='', encoding='utf-8') + return """ + + """ % (output_encoded,) +diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py +index 4884b07c95b9c50..3ace949afd403eb 100644 +--- a/Lib/test/test_http_cookies.py ++++ b/Lib/test/test_http_cookies.py +@@ -1,11 +1,11 @@ + # Simple test suite for http/cookies.py +-import base64 + import copy + import unittest + import doctest + from http import cookies + import pickle + from test import support ++import urllib.parse + + + class CookieTests(unittest.TestCase): +@@ -152,19 +152,19 @@ def test_load(self): + + self.assertEqual(C.output(['path']), + 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme') +- cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii') ++ cookie_encoded = urllib.parse.quote('Customer="WILE_E_COYOTE"; Path=/acme; Version=1', safe='', encoding='utf-8') + self.assertEqual(C.js_output(), fr""" + + """) +- cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii') ++ cookie_encoded = urllib.parse.quote('Customer="WILE_E_COYOTE"; Path=/acme', safe='', encoding='utf-8') + self.assertEqual(C.js_output(['path']), fr""" + + """) +@@ -269,19 +269,19 @@ def test_quoted_meta(self): + + self.assertEqual(C.output(['path']), + 'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme') +- expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1').decode('ascii') ++ expected_encoded_cookie = urllib.parse.quote('Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1', safe='', encoding='utf-8') + self.assertEqual(C.js_output(), fr""" + + """) +- expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii') ++ expected_encoded_cookie = urllib.parse.quote('Customer=\"WILE_E_COYOTE\"; Path=/acme', safe='', encoding='utf-8') + self.assertEqual(C.js_output(['path']), fr""" + + """) +@@ -372,13 +372,14 @@ def test_setter(self): + self.assertEqual( + M.output(), + "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i)) +- expected_encoded_cookie = base64.b64encode( +- ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii") +- ).decode('ascii') ++ expected_encoded_cookie = urllib.parse.quote( ++ "%s=%s; Path=/foo" % (i, "%s_coded_val" % i), ++ safe='', encoding='utf-8', ++ ) + expected_js_output = """ + + """ % (expected_encoded_cookie,) diff --git a/SPECS/python3.14.spec b/SPECS/python3.14.spec index f522c66..8a65aaa 100644 --- a/SPECS/python3.14.spec +++ b/SPECS/python3.14.spec @@ -45,11 +45,11 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well -%global general_version %{pybasever}.4 +%global general_version %{pybasever}.5 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 3%{?dist} +Release: 1%{?dist} License: Python-2.0.1 @@ -109,31 +109,30 @@ License: Python-2.0.1 # This needs to be manually updated when we update Python. # Explore the sources tarball (you need the version before %%prep is executed): # $ tar -tf Python-%%{upstream_version}.tar.xz | grep whl -%global pip_version 26.0.1 +%global pip_version 26.1.1 %global setuptools_version 79.0.1 # All of those also include a list of indirect bundled libs: # pip # $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt) %global pip_bundled_provides %{expand: Provides: bundled(python3dist(cachecontrol)) = 0.14.4 -Provides: bundled(python3dist(certifi)) = 2026.1.4 -Provides: bundled(python3dist(dependency-groups)) = 1.3.1 +Provides: bundled(python3dist(certifi)) = 2026.2.25 Provides: bundled(python3dist(distlib)) = 0.4 Provides: bundled(python3dist(distro)) = 1.9 Provides: bundled(python3dist(idna)) = 3.11 Provides: bundled(python3dist(msgpack)) = 1.1.2 -Provides: bundled(python3dist(packaging)) = 26 +Provides: bundled(python3dist(packaging)) = 26.2 Provides: bundled(python3dist(platformdirs)) = 4.5.1 Provides: bundled(python3dist(pygments)) = 2.19.2 Provides: bundled(python3dist(pyproject-hooks)) = 1.2 -Provides: bundled(python3dist(requests)) = 2.32.5 +Provides: bundled(python3dist(requests)) = 2.33.1 Provides: bundled(python3dist(resolvelib)) = 1.2.1 Provides: bundled(python3dist(rich)) = 14.2 Provides: bundled(python3dist(setuptools)) = 70.3 -Provides: bundled(python3dist(tomli)) = 2.3 +Provides: bundled(python3dist(tomli)) = 2.3.1 Provides: bundled(python3dist(tomli-w)) = 1.2 Provides: bundled(python3dist(truststore)) = 0.10.4 -Provides: bundled(python3dist(urllib3)) = 1.26.20 +Provides: bundled(python3dist(urllib3)) = 2.6.3 } # setuptools # vendor.txt not in .whl @@ -435,29 +434,11 @@ Patch475: 00475-cve-2025-15367.patch # direct call to the check function. Patch477: 00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch -# 00479 # 97404b2cf62e545c2d41be7ccfed4e74da9ee665 -# CVE-2026-1502 -# -# Reject CR/LF in HTTP tunnel request headers -Patch479: 00479-cve-2026-1502.patch - -# 00480 # 858691f36890b33e713f330d24c6670329695c2e -# CVE-2026-4786 -# -# Fix webbrowser `%%action` substitution bypass of dash-prefix check -Patch480: 00480-cve-2026-4786.patch - -# 00481 # 4c1fd39918651c4559a4835d42b86639a192c2c5 -# CVE-2026-5713 -# -# Validate remote debug offset tables on load -Patch481: 00481-cve-2026-5713.patch - -# 00482 # 69f14bc306fc62400d45565faa980b77858b9151 -# CVE-2026-6100 -# -# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor -Patch482: 00482-cve-2026-6100.patch +# 00487 # +# Fixup for CVE-2026-6019 +# Use decodeURIComponent() for UTF-8 support in js_output() +# Resolved upstream: https://github.com/python/cpython/issues/149144 +Patch487: 00487-fixup-for-CVE-2026-6019.patch # (New patches go here ^^^) # @@ -1981,6 +1962,16 @@ CheckPython freethreading # ====================================================== %changelog +* Tue Jun 09 2026 Miro Hrončok - 3.14.5-1 +- Update to 3.14.5 +Resolves: RHEL-180642 + +* Tue Jun 09 2026 Miro Hrončok - 3.14.5~rc1-1 +- Update to 3.14.5rc1 +- Move back to the generational from the incremental garbage collector +- Security fix for CVE-2026-6019 +Resolves: RHEL-180642 + * Mon Jun 01 2026 Lukáš Zachar - 3.14.4-3 - Depend on sqlite-libs with (de)serialize API Resolves: RHEL-180476