Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b3248d1cd | ||
|
|
87a6dd2b48 | ||
|
|
2066728bf4 |
1
.fmf/version
Normal file
1
.fmf/version
Normal file
@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
SOURCES/Python-3.14.4.tar.xz
|
/*.tar.*
|
||||||
|
/*.src.rpm
|
||||||
|
/results_python3*
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
567ffd2b4116db4cbcd3fb7adb304ec32687f99f SOURCES/Python-3.14.4.tar.xz
|
|
||||||
@ -41,7 +41,7 @@ index 5c10bcedc6..1fd7a273b5 100644
|
|||||||
result = BytesIO()
|
result = BytesIO()
|
||||||
xmlgen = XMLGenerator(result)
|
xmlgen = XMLGenerator(result)
|
||||||
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
|
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
|
--- a/Lib/test/test_xml_etree.py
|
||||||
+++ b/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):
|
@@ -1573,9 +1573,13 @@ def test_simple_xml(self, chunk_size=None, flush=False):
|
||||||
@ -81,10 +81,10 @@ index 2a17c891dd..64017c666c 100644
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
diff --git a/Makefile.pre.in b/Makefile.pre.in
|
diff --git a/Makefile.pre.in b/Makefile.pre.in
|
||||||
index 80a1b590c2..f28f562930 100644
|
index da6d7c3315..92a6825b5e 100644
|
||||||
--- a/Makefile.pre.in
|
--- a/Makefile.pre.in
|
||||||
+++ b/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:
|
# Local Variables:
|
||||||
# mode: makefile
|
# mode: makefile
|
||||||
# End:
|
# End:
|
||||||
124
00487-fixup-for-CVE-2026-6019.patch
Normal file
124
00487-fixup-for-CVE-2026-6019.patch
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
From 4f17f6e04534d93fbd23862f9f290d3a281ab30b Mon Sep 17 00:00:00 2001
|
||||||
|
From: Petr Viktorin <encukou@gmail.com>
|
||||||
|
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 <seth@python.org>
|
||||||
|
---
|
||||||
|
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 """
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob(\"%s\");
|
||||||
|
+ document.cookie = decodeURIComponent(\"%s\");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""" % (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"""
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob("{cookie_encoded}");
|
||||||
|
+ document.cookie = decodeURIComponent("{cookie_encoded}");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""")
|
||||||
|
- 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"""
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob("{cookie_encoded}");
|
||||||
|
+ document.cookie = decodeURIComponent("{cookie_encoded}");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""")
|
||||||
|
@@ -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"""
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob("{expected_encoded_cookie}");
|
||||||
|
+ document.cookie = decodeURIComponent("{expected_encoded_cookie}");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""")
|
||||||
|
- 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"""
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob("{expected_encoded_cookie}");
|
||||||
|
+ document.cookie = decodeURIComponent("{expected_encoded_cookie}");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""")
|
||||||
|
@@ -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 = """
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- begin hiding
|
||||||
|
- document.cookie = atob("%s");
|
||||||
|
+ document.cookie = decodeURIComponent("%s");
|
||||||
|
// end hiding -->
|
||||||
|
</script>
|
||||||
|
""" % (expected_encoded_cookie,)
|
||||||
@ -1,107 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Seth Larson <seth@python.org>
|
|
||||||
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 <illia.volochii@gmail.com>
|
|
||||||
---
|
|
||||||
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.
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Stan Ulbrych <stan@python.org>
|
|
||||||
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.
|
|
||||||
@ -1,587 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Pablo Galindo Salgado <Pablogsal@gmail.com>
|
|
||||||
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 <internal/pycore_interpframe.h> // FRAME_OWNED_BY_CSTACK
|
|
||||||
#include <internal/pycore_llist.h> // struct llist_node
|
|
||||||
#include <internal/pycore_stackref.h> // Py_TAG_BITS
|
|
||||||
+#include <internal/pycore_tstate.h> // _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;
|
|
||||||
@ -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 <stan@python.org>
|
|
||||||
---
|
|
||||||
.../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;
|
|
||||||
}
|
|
||||||
50
expat-requires.py
Executable file
50
expat-requires.py
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
import pathlib
|
||||||
|
import pyexpat
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
# This will determine the version of currently installed expat
|
||||||
|
EXPAT_VERSION = pyexpat.EXPAT_VERSION.removeprefix('expat_')
|
||||||
|
MAJOR, MINOR, PATCH = (int(i) for i in EXPAT_VERSION.split('.'))
|
||||||
|
EXPAT_COMBINED_VERSION = 10000*MAJOR + 100*MINOR + PATCH
|
||||||
|
|
||||||
|
# For the listed files, we find all XML_COMBINED_VERSION-based #ifs
|
||||||
|
SRC = pathlib.Path.cwd()
|
||||||
|
SOURCES = [
|
||||||
|
SRC / 'Modules/pyexpat.c',
|
||||||
|
SRC / 'Modules/clinic/pyexpat.c.h',
|
||||||
|
]
|
||||||
|
versions = set()
|
||||||
|
for source in SOURCES:
|
||||||
|
for line in source.read_text().splitlines():
|
||||||
|
if 'XML_COMBINED_VERSION' not in line:
|
||||||
|
continue
|
||||||
|
words = line.split()
|
||||||
|
if words[0] == '#define':
|
||||||
|
continue
|
||||||
|
if len(words) != 4:
|
||||||
|
continue
|
||||||
|
if words[0] not in ('#if', '#elif'):
|
||||||
|
continue
|
||||||
|
if words[1].startswith('(') and words[-1].endswith(')'):
|
||||||
|
words[1] = words[1][1:]
|
||||||
|
words[-1] = words[-1][:-1]
|
||||||
|
if words[1] == 'XML_COMBINED_VERSION':
|
||||||
|
version = int(words[3])
|
||||||
|
if words[2] == '>':
|
||||||
|
versions.add(version+1)
|
||||||
|
continue
|
||||||
|
if words[2] == '>=':
|
||||||
|
versions.add(version)
|
||||||
|
continue
|
||||||
|
raise ValueError(
|
||||||
|
'Unknown line with XML_COMBINED_VERSION, adjust this script:\n\n'
|
||||||
|
f'{line}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# We need the highest satisfiable version used in the #ifs, in x.y.z notation
|
||||||
|
v = max({v for v in versions if v <= EXPAT_COMBINED_VERSION})
|
||||||
|
major, minor_patch = divmod(v, 10000)
|
||||||
|
minor, patch = divmod(minor_patch, 100)
|
||||||
|
print(f"{major}.{minor}.{patch}")
|
||||||
|
|
||||||
6
gating.yaml
Normal file
6
gating.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
--- !Policy
|
||||||
|
product_versions:
|
||||||
|
- rhel-*
|
||||||
|
decision_context: osci_compose_gate
|
||||||
|
rules:
|
||||||
|
- !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional}
|
||||||
80
plan.fmf
Normal file
80
plan.fmf
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
execute:
|
||||||
|
how: tmt
|
||||||
|
|
||||||
|
provision:
|
||||||
|
hardware:
|
||||||
|
memory: '>= 3 GB'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
pybasever: '3.14'
|
||||||
|
|
||||||
|
discover:
|
||||||
|
- name: tests_python
|
||||||
|
how: shell
|
||||||
|
url: https://gitlab.com/redhat/centos-stream/tests/python.git
|
||||||
|
tests:
|
||||||
|
- name: smoke
|
||||||
|
path: /smoke
|
||||||
|
test: "VERSION=${pybasever} CYTHON=true ./venv.sh"
|
||||||
|
- name: smoke_virtualenv
|
||||||
|
path: /smoke
|
||||||
|
test: "VERSION=${pybasever} METHOD=virtualenv CYTHON=true ./venv.sh"
|
||||||
|
- name: debugsmoke
|
||||||
|
path: /smoke
|
||||||
|
test: "PYTHON=python${pybasever}d TOX=false VERSION=${pybasever} CYTHON=true ./venv.sh"
|
||||||
|
- name: selftest
|
||||||
|
path: /selftest
|
||||||
|
test: "VERSION=${pybasever} X='-i test_check_probes' ./parallel.sh"
|
||||||
|
- name: debugtest
|
||||||
|
path: /selftest
|
||||||
|
# test_base_interpreter: https://github.com/python/cpython/issues/131372
|
||||||
|
test: "VERSION=${pybasever} PYTHON=python${pybasever}d X='-i test_check_probes -i test_base_interpreter' ./parallel.sh"
|
||||||
|
- name: freethreadingtest
|
||||||
|
path: /selftest
|
||||||
|
test: "VERSION=${pybasever}t X='-i test_check_probes -i test_base_interpreter' ./parallel.sh"
|
||||||
|
- name: optimizedflags
|
||||||
|
path: /flags
|
||||||
|
test: "python${pybasever} ./assertflags.py -O3 CFLAGS PY_BUILTIN_MODULE_CFLAGS PY_CFLAGS PY_CORE_CFLAGS PY_CFLAGS_NODIST PY_STDMODULE_CFLAGS"
|
||||||
|
- name: debugflags
|
||||||
|
path: /flags
|
||||||
|
test: "python${pybasever}d ./assertflags.py -O0 CFLAGS PY_BUILTIN_MODULE_CFLAGS PY_CFLAGS PY_CORE_CFLAGS PY_CFLAGS_NODIST PY_STDMODULE_CFLAGS"
|
||||||
|
- name: freethreadingflags
|
||||||
|
path: /flags
|
||||||
|
test: "python${pybasever}t ./assertflags.py -O3 CFLAGS PY_BUILTIN_MODULE_CFLAGS PY_CFLAGS PY_CORE_CFLAGS PY_CFLAGS_NODIST PY_STDMODULE_CFLAGS"
|
||||||
|
- name: freethreadingdebugflags
|
||||||
|
path: /flags
|
||||||
|
test: "python${pybasever}td ./assertflags.py -O0 CFLAGS PY_BUILTIN_MODULE_CFLAGS PY_CFLAGS PY_CORE_CFLAGS PY_CFLAGS_NODIST PY_STDMODULE_CFLAGS"
|
||||||
|
- name: marshalparser
|
||||||
|
path: /marshalparser
|
||||||
|
test: "VERSION=${pybasever} SAMPLE=10 ./test_marshalparser_compatibility.sh"
|
||||||
|
|
||||||
|
prepare:
|
||||||
|
- name: Install dependencies
|
||||||
|
how: install
|
||||||
|
package:
|
||||||
|
- 'https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm'
|
||||||
|
- gcc # for extension building in venv and selftest
|
||||||
|
- gcc-c++ # for test_cppext
|
||||||
|
- gdb # for test_gdb
|
||||||
|
- "python${pybasever}" # the test subject
|
||||||
|
- "python${pybasever}-debug" # for leak testing
|
||||||
|
- "python${pybasever}-devel" # for extension building in venv and selftest
|
||||||
|
- "python${pybasever}-tkinter" # for selftest
|
||||||
|
- "python${pybasever}-test" # for selftest
|
||||||
|
- "python${pybasever}-freethreading" # for -O... flag test
|
||||||
|
- "python${pybasever}-freethreading-debug" # for -O... flag test
|
||||||
|
- "python${pybasever}-freethreading-tkinter" # for freethreadingtest
|
||||||
|
- "python${pybasever}-freethreading-test" # for freethreadingtest
|
||||||
|
- tox # for venv tests
|
||||||
|
- virtualenv # for virtualenv tests
|
||||||
|
- glibc-all-langpacks # for locale tests
|
||||||
|
- marshalparser # for testing compatibility (magic numbers) with marshalparser
|
||||||
|
- rpm # for debugging
|
||||||
|
- dnf # for upgrade
|
||||||
|
- name: Update packages
|
||||||
|
how: shell
|
||||||
|
script: dnf upgrade -y
|
||||||
|
- name: rpm_qa
|
||||||
|
order: 100
|
||||||
|
how: shell
|
||||||
|
script: rpm -qa | sort | tee $TMT_PLAN_DATA/rpmqa.txt
|
||||||
@ -45,11 +45,11 @@ URL: https://www.python.org/
|
|||||||
|
|
||||||
# WARNING When rebasing to a new Python version,
|
# WARNING When rebasing to a new Python version,
|
||||||
# remember to update the python3-docs package as well
|
# remember to update the python3-docs package as well
|
||||||
%global general_version %{pybasever}.4
|
%global general_version %{pybasever}.5
|
||||||
#global prerel ...
|
#global prerel ...
|
||||||
%global upstream_version %{general_version}%{?prerel}
|
%global upstream_version %{general_version}%{?prerel}
|
||||||
Version: %{general_version}%{?prerel:~%{prerel}}
|
Version: %{general_version}%{?prerel:~%{prerel}}
|
||||||
Release: 3%{?dist}
|
Release: 1%{?dist}
|
||||||
License: Python-2.0.1
|
License: Python-2.0.1
|
||||||
|
|
||||||
|
|
||||||
@ -109,31 +109,30 @@ License: Python-2.0.1
|
|||||||
# This needs to be manually updated when we update Python.
|
# This needs to be manually updated when we update Python.
|
||||||
# Explore the sources tarball (you need the version before %%prep is executed):
|
# Explore the sources tarball (you need the version before %%prep is executed):
|
||||||
# $ tar -tf Python-%%{upstream_version}.tar.xz | grep whl
|
# $ 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
|
%global setuptools_version 79.0.1
|
||||||
# All of those also include a list of indirect bundled libs:
|
# All of those also include a list of indirect bundled libs:
|
||||||
# pip
|
# pip
|
||||||
# $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt)
|
# $ %%{_rpmconfigdir}/pythonbundles.py <(unzip -p Lib/ensurepip/_bundled/pip-*.whl pip/_vendor/vendor.txt)
|
||||||
%global pip_bundled_provides %{expand:
|
%global pip_bundled_provides %{expand:
|
||||||
Provides: bundled(python3dist(cachecontrol)) = 0.14.4
|
Provides: bundled(python3dist(cachecontrol)) = 0.14.4
|
||||||
Provides: bundled(python3dist(certifi)) = 2026.1.4
|
Provides: bundled(python3dist(certifi)) = 2026.2.25
|
||||||
Provides: bundled(python3dist(dependency-groups)) = 1.3.1
|
|
||||||
Provides: bundled(python3dist(distlib)) = 0.4
|
Provides: bundled(python3dist(distlib)) = 0.4
|
||||||
Provides: bundled(python3dist(distro)) = 1.9
|
Provides: bundled(python3dist(distro)) = 1.9
|
||||||
Provides: bundled(python3dist(idna)) = 3.11
|
Provides: bundled(python3dist(idna)) = 3.11
|
||||||
Provides: bundled(python3dist(msgpack)) = 1.1.2
|
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(platformdirs)) = 4.5.1
|
||||||
Provides: bundled(python3dist(pygments)) = 2.19.2
|
Provides: bundled(python3dist(pygments)) = 2.19.2
|
||||||
Provides: bundled(python3dist(pyproject-hooks)) = 1.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(resolvelib)) = 1.2.1
|
||||||
Provides: bundled(python3dist(rich)) = 14.2
|
Provides: bundled(python3dist(rich)) = 14.2
|
||||||
Provides: bundled(python3dist(setuptools)) = 70.3
|
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(tomli-w)) = 1.2
|
||||||
Provides: bundled(python3dist(truststore)) = 0.10.4
|
Provides: bundled(python3dist(truststore)) = 0.10.4
|
||||||
Provides: bundled(python3dist(urllib3)) = 1.26.20
|
Provides: bundled(python3dist(urllib3)) = 2.6.3
|
||||||
}
|
}
|
||||||
# setuptools
|
# setuptools
|
||||||
# vendor.txt not in .whl
|
# vendor.txt not in .whl
|
||||||
@ -278,7 +277,7 @@ BuildRequires: pkgconfig
|
|||||||
BuildRequires: python-rpm-macros
|
BuildRequires: python-rpm-macros
|
||||||
BuildRequires: readline-devel
|
BuildRequires: readline-devel
|
||||||
BuildRequires: redhat-rpm-config >= 127
|
BuildRequires: redhat-rpm-config >= 127
|
||||||
BuildRequires: sqlite-devel >= 3.34.1-10
|
BuildRequires: sqlite-devel
|
||||||
BuildRequires: systemtap-sdt-devel
|
BuildRequires: systemtap-sdt-devel
|
||||||
BuildRequires: tcl-devel
|
BuildRequires: tcl-devel
|
||||||
BuildRequires: tk-devel
|
BuildRequires: tk-devel
|
||||||
@ -341,6 +340,9 @@ Source0: %{url}ftp/python/%{general_version}/Python-%{upstream_version}.tar.xz
|
|||||||
# Originally written by bkabrda
|
# Originally written by bkabrda
|
||||||
Source8: check-pyc-timestamps.py
|
Source8: check-pyc-timestamps.py
|
||||||
|
|
||||||
|
# A script that determines the required expat version
|
||||||
|
Source9: expat-requires.py
|
||||||
|
|
||||||
# Desktop menu entry for idle3
|
# Desktop menu entry for idle3
|
||||||
Source10: idle3.desktop
|
Source10: idle3.desktop
|
||||||
|
|
||||||
@ -435,29 +437,11 @@ Patch475: 00475-cve-2025-15367.patch
|
|||||||
# direct call to the check function.
|
# direct call to the check function.
|
||||||
Patch477: 00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch
|
Patch477: 00477-raise-an-error-when-importing-stdlib-modules-compiled-for-a-different-python-version.patch
|
||||||
|
|
||||||
# 00479 # 97404b2cf62e545c2d41be7ccfed4e74da9ee665
|
# 00487 #
|
||||||
# CVE-2026-1502
|
# Fixup for CVE-2026-6019
|
||||||
#
|
# Use decodeURIComponent() for UTF-8 support in js_output()
|
||||||
# Reject CR/LF in HTTP tunnel request headers
|
# Resolved upstream: https://github.com/python/cpython/issues/149144
|
||||||
Patch479: 00479-cve-2026-1502.patch
|
Patch487: 00487-fixup-for-CVE-2026-6019.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 ^^^)
|
# (New patches go here ^^^)
|
||||||
#
|
#
|
||||||
@ -639,8 +623,20 @@ Recommends: (%{pkgname}-tkinter%{?_isa} if tk%{?_isa})
|
|||||||
# The zoneinfo module needs tzdata
|
# The zoneinfo module needs tzdata
|
||||||
Requires: tzdata
|
Requires: tzdata
|
||||||
|
|
||||||
# The sqlite3 needs version with (de)serialize API
|
# The requirement on libexpat is generated, but we need to version it.
|
||||||
Requires: sqlite-libs >= 3.34.1-10
|
# When built with a specific expat version, but installed with an older one,
|
||||||
|
# we sometimes get:
|
||||||
|
# ImportError: /usr/lib64/python3.X/lib-dynload/pyexpat.cpython-....so:
|
||||||
|
# undefined symbol: XML_...
|
||||||
|
# The pyexpat module has build-time checks for expat version to only use the
|
||||||
|
# available symbols. However, there is no runtime protection, so when the module
|
||||||
|
# is later installed with an older expat, it may error due to undefined symbols.
|
||||||
|
# This breaks many things, including python -m venv.
|
||||||
|
# We avoid this problem by requiring expat equal or greater than the latest known
|
||||||
|
# version which introduced new symbols used by Python.
|
||||||
|
# Other subpackages (like -debug) also need this, but they all depend on -libs.
|
||||||
|
%global expat_min_version 2.7.2
|
||||||
|
Requires: expat%{?_isa} >= %{expat_min_version}
|
||||||
|
|
||||||
%description -n %{pkgname}-libs
|
%description -n %{pkgname}-libs
|
||||||
This package contains runtime libraries for use by Python:
|
This package contains runtime libraries for use by Python:
|
||||||
@ -665,10 +661,6 @@ Requires: (python3-rpm-macros if rpm-build)
|
|||||||
# On Fedora, we keep this to avoid one additional round of %%generate_buildrequires.
|
# On Fedora, we keep this to avoid one additional round of %%generate_buildrequires.
|
||||||
%{!?rhel:Requires: (pyproject-rpm-macros if rpm-build)}
|
%{!?rhel:Requires: (pyproject-rpm-macros if rpm-build)}
|
||||||
|
|
||||||
# We provide the python3.14-rpm-macros here to make it possible to
|
|
||||||
# BuildRequire them in the same manner as RHEL8.
|
|
||||||
Provides: %{pkgname}-rpm-macros = %{version}-%{release}
|
|
||||||
|
|
||||||
%unversioned_obsoletes_of_python3_X_if_main devel
|
%unversioned_obsoletes_of_python3_X_if_main devel
|
||||||
|
|
||||||
%if %{with main_python}
|
%if %{with main_python}
|
||||||
@ -842,6 +834,7 @@ License: %{libs_license} AND Apache-2.0 AND ISC AND LGPL-2.1-only AND MPL-2.0 AN
|
|||||||
# See the comments in the definition of main -libs subpackage for detailed explanations
|
# See the comments in the definition of main -libs subpackage for detailed explanations
|
||||||
Provides: bundled(mimalloc) = 2.12
|
Provides: bundled(mimalloc) = 2.12
|
||||||
Requires: tzdata
|
Requires: tzdata
|
||||||
|
Requires: expat%{?_isa} >= %{expat_min_version}
|
||||||
|
|
||||||
# There are files in the standard library that have python shebang.
|
# There are files in the standard library that have python shebang.
|
||||||
# We've filtered the automatic requirement out so libs are installable without
|
# We've filtered the automatic requirement out so libs are installable without
|
||||||
@ -1466,6 +1459,12 @@ for Module in %{buildroot}/%{dynload_dir}/*.so ; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Check the expat compatibility
|
||||||
|
expat_found=$(LD_LIBRARY_PATH="%{buildroot}%{_libdir}" PYTHONPATH="%{buildroot}%{pylibdir}" %{buildroot}%{_bindir}/python%{pybasever} %{SOURCE9})
|
||||||
|
if [ "${expat_found}" != "%{expat_min_version}" ]; then
|
||||||
|
echo "Found expat version is different than the declared one, found: ${expat_found}" ; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
# ======================================================
|
# ======================================================
|
||||||
# Running the upstream test suite
|
# Running the upstream test suite
|
||||||
@ -1981,33 +1980,42 @@ CheckPython freethreading
|
|||||||
# ======================================================
|
# ======================================================
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Mon Jun 01 2026 Lukáš Zachar <lzachar@redhat.com> - 3.14.4-3
|
* Tue Jun 09 2026 Miro Hrončok <mhroncok@redhat.com> - 3.14.5-1
|
||||||
- Depend on sqlite-libs with (de)serialize API
|
- Update to 3.14.5
|
||||||
Resolves: RHEL-180476
|
Resolves: RHEL-176147
|
||||||
|
|
||||||
|
* Tue Jun 09 2026 Miro Hrončok <mhroncok@redhat.com> - 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-176147
|
||||||
|
|
||||||
* Thu Apr 16 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.14.4-2
|
* Thu Apr 16 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.14.4-2
|
||||||
- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-5713, CVE-2026-6100
|
- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-5713, CVE-2026-6100
|
||||||
Resolves: RHEL-167918, RHEL-168160
|
Resolves: RHEL-168121, RHEL-167887
|
||||||
|
|
||||||
* Wed Apr 08 2026 Karolina Surma <ksurma@redhat.com> - 3.14.4-1
|
* Wed Apr 08 2026 Karolina Surma <ksurma@redhat.com> - 3.14.4-1
|
||||||
- Update to Python 3.14.4
|
- Update to Python 3.14.4
|
||||||
- Security fixes for CVE-2026-2297, CVE-2026-3644, CVE-2026-4224, CVE-2026-0865
|
- Security fixes for CVE-2026-2297, CVE-2026-3644, CVE-2026-4224, CVE-2026-0865
|
||||||
Related: RHEL-167918, RHEL-168160
|
Related: RHEL-168121, RHEL-167887
|
||||||
|
|
||||||
* Thu Mar 26 2026 Lumír Balhar <lbalhar@redhat.com> - 3.14.3-2
|
* Thu Mar 26 2026 Lumír Balhar <lbalhar@redhat.com> - 3.14.3-2
|
||||||
- Security fix for CVE-2026-4519
|
- Security fix for CVE-2026-4519
|
||||||
Resolves: RHEL-158115
|
Resolves: RHEL-158114
|
||||||
|
|
||||||
* Wed Feb 04 2026 Karolina Surma <ksurma@redhat.com> - 3.14.3-1
|
* Wed Feb 04 2026 Karolina Surma <ksurma@redhat.com> - 3.14.3-1
|
||||||
- Update to Python 3.14.3
|
- Update to Python 3.14.3
|
||||||
- Security fixes for CVE-2025-11468, CVE-2026-0672, CVE-2026-0865,
|
- Security fixes for CVE-2025-11468, CVE-2026-0672, CVE-2026-0865,
|
||||||
CVE-2025-15282, CVE-2026-1299, CVE-2025-15366, CVE-2025-15367
|
CVE-2025-15282, CVE-2026-1299, CVE-2025-15366, CVE-2025-15367
|
||||||
Resolves: RHEL-144896, RHEL-143115, RHEL-143173
|
Resolves: RHEL-144855, RHEL-143058, RHEL-143111
|
||||||
|
|
||||||
* Fri Jan 09 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.14.2-3
|
* Mon Jan 19 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.14.2-3
|
||||||
- Support OpenSSL FIPS mode
|
- Support OpenSSL FIPS mode
|
||||||
- Disable the builtin hashlib hashes except blake2
|
- Disable the builtin hashlib hashes except blake2
|
||||||
Related: RHEL-120823
|
Related: RHEL-120788
|
||||||
|
|
||||||
|
* Mon Jan 12 2026 Karolina Surma <ksurma@redhat.com> - 3.14.2-2
|
||||||
|
- Explicitly require expat >= 2.7.2
|
||||||
|
|
||||||
* Fri Dec 05 2025 Miro Hrončok <mhroncok@redhat.com> - 3.14.2-1
|
* Fri Dec 05 2025 Miro Hrončok <mhroncok@redhat.com> - 3.14.2-1
|
||||||
- Update to Python 3.14.2
|
- Update to Python 3.14.2
|
||||||
37
rpminspect.yaml
Normal file
37
rpminspect.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# exclude test XML data (not always valid) from XML validity check:
|
||||||
|
xml:
|
||||||
|
ignore:
|
||||||
|
- '/usr/lib*/python*/test/xmltestdata/*'
|
||||||
|
- '/usr/lib*/python*/test/xmltestdata/*/*'
|
||||||
|
|
||||||
|
# exclude _socket from ipv4 only functions check, it has both ipv4 and ipv6 only
|
||||||
|
badfuncs:
|
||||||
|
allowed:
|
||||||
|
'/usr/lib*/python*/lib-dynload/_socket.*':
|
||||||
|
- inet_aton
|
||||||
|
- inet_ntoa
|
||||||
|
|
||||||
|
# exclude the debug build from annocheck entirely
|
||||||
|
annocheck:
|
||||||
|
ignore:
|
||||||
|
- '/usr/bin/python*d'
|
||||||
|
- '/usr/lib*/libpython*d.so.1.0'
|
||||||
|
- '/usr/lib*/python*/lib-dynload/*.cpython-*d-*-*-*.so'
|
||||||
|
|
||||||
|
# don't report changed content of compiled files
|
||||||
|
# that is expected with every toolchain update and not reproducible yet
|
||||||
|
changedfiles:
|
||||||
|
# note that this is a posix regex, so no \d
|
||||||
|
exclude_path: (\.so(\.[0-9]+(\.[0-9]+)?)?$|^/usr/bin/python[0-9]+\.[0-9]+d?m?$)
|
||||||
|
|
||||||
|
# files change size all the time, we don't need to VERIFY it
|
||||||
|
# however, the INFO is useful, so we don't disable the check entirely
|
||||||
|
filesize:
|
||||||
|
# artificially large number, TODO a better way
|
||||||
|
size_threshold: 100000
|
||||||
|
|
||||||
|
|
||||||
|
# completely disabled inspections:
|
||||||
|
inspections:
|
||||||
|
# we know about our patches, no need to report anything
|
||||||
|
patches: off
|
||||||
106
rpmlint.toml
Normal file
106
rpmlint.toml
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
Filters = [
|
||||||
|
|
||||||
|
# KNOWN BUGS:
|
||||||
|
# https://bugzilla.redhat.com/show_bug.cgi?id=1489816
|
||||||
|
'crypto-policy-non-compliance-openssl',
|
||||||
|
|
||||||
|
|
||||||
|
# TESTS:
|
||||||
|
'(zero-length|pem-certificate|uncompressed-zip) /usr/lib(64)?/python3\.\d+t?/test',
|
||||||
|
|
||||||
|
|
||||||
|
# OTHER DELIBERATES:
|
||||||
|
# chroot function
|
||||||
|
'missing-call-to-chdir-with-chroot',
|
||||||
|
|
||||||
|
# gethostbyname function calls gethostbyname
|
||||||
|
'(E|W): binary-or-shlib-calls-gethostbyname /usr/lib(64)?/python3\.\d+t?/lib-dynload/_socket\.',
|
||||||
|
|
||||||
|
# intentionally unversioned and selfobsoleted
|
||||||
|
'unversioned-explicit-obsoletes python',
|
||||||
|
'unversioned Obsoletes: Obsoletes: python3\.\d+$',
|
||||||
|
'self-obsoletion python3\.\d+(-\S+)? obsoletes python3\.\d+(-\S+)?',
|
||||||
|
|
||||||
|
# intentionally hardcoded
|
||||||
|
'hardcoded-library-path in %{_prefix}/lib/(debug/%{_libdir}|python%{pybasever})',
|
||||||
|
|
||||||
|
# we have non binary stuff, python files
|
||||||
|
'only-non-binary-in-usr-lib',
|
||||||
|
|
||||||
|
# some devel files that are deliberately needed
|
||||||
|
'devel-file-in-non-devel-package /usr/include/python3\.\d+m?t?/pyconfig-(32|64)\.h',
|
||||||
|
'devel-file-in-non-devel-package /usr/lib(64)?/python3\.\d+t?/distutils/tests/xxmodule\.c',
|
||||||
|
# ...or are used as test data
|
||||||
|
'devel-file-in-non-devel-package /usr/lib(64)?/python3\.\d+t?/test',
|
||||||
|
|
||||||
|
# some bytecode is shipped without sources on purpose, as a space optimization
|
||||||
|
# if this regex needs to be relaxed in the future, make sure it **does not** match pyc files in __pycache__
|
||||||
|
'python-bytecode-without-source /usr/lib(64)?/python3\.\d+t?/(encodings|pydoc_data)/[^/]+.pyc',
|
||||||
|
|
||||||
|
# DUPLICATE FILES
|
||||||
|
# test data are often duplicated
|
||||||
|
'(E|W): files-duplicate /usr/lib(64)?/python3\.\d+t?/(test|__phello__)/',
|
||||||
|
# duplicated inits or mains are also common
|
||||||
|
'(E|W): files-duplicate .+__init__\.py.+__init__\.py',
|
||||||
|
'(E|W): files-duplicate .+__main__\.py.+__main__\.py',
|
||||||
|
# files in the debugsource package
|
||||||
|
'(E|W): files-duplicate /usr/src/debug',
|
||||||
|
# general waste report
|
||||||
|
'(E|W): files-duplicated-waste',
|
||||||
|
|
||||||
|
# SORRY, NOT SORRY:
|
||||||
|
# manual pages
|
||||||
|
'no-manual-page-for-binary (idle|pydoc|pyvenv|2to3|python3?-debug|pathfix|msgfmt|pygettext)',
|
||||||
|
'no-manual-page-for-binary python3?.*-config$',
|
||||||
|
'no-manual-page-for-binary python3\.\d+t?dm?$',
|
||||||
|
|
||||||
|
# missing documentation from subpackages
|
||||||
|
'^python3(\.\d+)?-(freethreading(-debug)?|debug|tkinter|test|idle)\.[^:]+: (E|W): no-documentation',
|
||||||
|
|
||||||
|
# platform python is obsoleted, but not provided
|
||||||
|
'obsolete-not-provided platform-python',
|
||||||
|
|
||||||
|
# we have extra tokens at the end of %endif/%else directives, we consider them useful
|
||||||
|
'extra tokens at the end of %(endif|else) directive',
|
||||||
|
|
||||||
|
|
||||||
|
# RPMLINT IMPERFECTIONS
|
||||||
|
# https://github.com/rpm-software-management/rpmlint/issues/780
|
||||||
|
'/usr/lib/debug',
|
||||||
|
|
||||||
|
# we provide python(abi) manually to be sure. createrepo will merge this with the automatic
|
||||||
|
'python3(\.\d+)?\.[^:-]+: (E|W): useless-provides python\(abi\)',
|
||||||
|
|
||||||
|
# debugsource and debuginfo have no docs
|
||||||
|
'^python3(\.\d+)?-debug(source|info)\.[^:]+: (E|W): no-documentation',
|
||||||
|
|
||||||
|
# this is OK for F28+
|
||||||
|
'library-without-ldconfig-post',
|
||||||
|
|
||||||
|
# freethreading/debug package contains devel and non-devel files
|
||||||
|
'python3(\.\d+)?-(freethreading(-debug)?|debug)\.[^:]+: (E|W): (non-)?devel-file-in-(non-)?devel-package',
|
||||||
|
|
||||||
|
# this goes to other subpackage, hence not actually dangling
|
||||||
|
'dangling-relative-symlink /usr/bin/python python3',
|
||||||
|
'dangling-relative-symlink /usr/share/man/man1/python\.1\.gz python3\.1\.gz',
|
||||||
|
'dangling-relative-symlink /usr/lib(64)?/pkgconfig/python-3\.\d+t?d?m?(-embed)?\.pc python-3\.\d+t?(-embed)?\.pc',
|
||||||
|
|
||||||
|
# the python-unversioned-command package contains dangling symlinks by design
|
||||||
|
'^python-unversioned-command\.[^:]+: (E|W): dangling-relative-symlink (/usr/bin/python \./python3|/usr/share/man/man1/python\.1\S* ./python3\.1\S*)$',
|
||||||
|
|
||||||
|
# we need this macro to evaluate, even if the line starts with #
|
||||||
|
'macro-in-comment %\{_pyconfig(32|64)_h\}',
|
||||||
|
|
||||||
|
# Python modules don't need to be linked against libc
|
||||||
|
# Since 3.8 they are no longer linked against libpython3.8.so.1.0
|
||||||
|
'(E|W): library-not-linked-against-libc /usr/lib(64)?/python3\.\d+/lib-dynload/',
|
||||||
|
'(E|W): shared-lib(rary)?-without-dependency-information /usr/lib(64)?/python3\.\d+/lib-dynload/',
|
||||||
|
|
||||||
|
# specfile-errors are listed twice, once with reason and once without
|
||||||
|
# we filter out the empty ones
|
||||||
|
'\bpython3(\.\d+)?\.(src|spec): (E|W): specfile-error\s+$',
|
||||||
|
|
||||||
|
# SPELLING ERRORS
|
||||||
|
'spelling-error .* en_US (bytecode|pyc|filename|tkinter|namespaces|pytest|unittest|gil) ',
|
||||||
|
|
||||||
|
]
|
||||||
Loading…
Reference in New Issue
Block a user