diff --git a/00479-cve-2026-1502.patch b/00479-cve-2026-1502.patch new file mode 100644 index 0000000..c820171 --- /dev/null +++ b/00479-cve-2026-1502.patch @@ -0,0 +1,107 @@ +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/00480-cve-2026-4786.patch b/00480-cve-2026-4786.patch new file mode 100644 index 0000000..1b51d5a --- /dev/null +++ b/00480-cve-2026-4786.patch @@ -0,0 +1,64 @@ +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/00481-cve-2026-5713.patch b/00481-cve-2026-5713.patch new file mode 100644 index 0000000..d5b0551 --- /dev/null +++ b/00481-cve-2026-5713.patch @@ -0,0 +1,587 @@ +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/00482-cve-2026-6100.patch b/00482-cve-2026-6100.patch new file mode 100644 index 0000000..0c42470 --- /dev/null +++ b/00482-cve-2026-6100.patch @@ -0,0 +1,64 @@ +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/python3.14.spec b/python3.14.spec index 2958963..de5d317 100644 --- a/python3.14.spec +++ b/python3.14.spec @@ -49,7 +49,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 1%{?dist} +Release: 2%{?dist} License: Python-2.0.1 @@ -438,6 +438,30 @@ 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 + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1975,6 +1999,10 @@ CheckPython freethreading # ====================================================== %changelog +* Thu Apr 16 2026 Charalampos Stratakis - 3.14.4-2 +- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-5713, CVE-2026-6100 +Resolves: RHEL-168121, RHEL-167887 + * Wed Apr 08 2026 Karolina Surma - 3.14.4-1 - Update to Python 3.14.4 - Security fixes for CVE-2026-2297, CVE-2026-3644, CVE-2026-4224, CVE-2026-0865