Skip to content

Commit e478d92

Browse files
mceplbmwiedemann
authored andcommitted
Update python311 to version 3.11.15 / rev 71 via SR 1368927
https://build.opensuse.org/request/show/1368927 by user mcepl + dimstar_suse - CVE-2026-3276: Fix O(n^2) canonical ordering in unicodedata.normalize() (bsc#1267581, gh#python/cpython#149079) CVE-2026-3276-On2-unicodedata-normalize.patch - CVE-2026-7774: tarfile.data_filter: validate written link target (bsc#1267821, gh#149486) CVE-2026-7774-tarfile-data_filter-symlink.patch - CVE-2026-0864: Normalize all line endings (CR, CRLF, and LF) in configparser (bsc#1269066, gh#python/cpython#143927) CVE-2026-0864-normalize-LFTAB-configparser.patch - CVE-2026-11972: Make tarfile._Stream.seek break at EOF (bsc#1269788, gh#python/cpython#151981) CVE-2026-11972-tarfile-Stream-seek-EOF.patch - CVE-2026-4360: Pass filter_function to TarFile._extract_one() during .extract() (bsc#1269959, gh#python/cpython#151987) CVE-2026-4360-filter_function-TarFile-extracton
1 parent b739b24 commit e478d92

16 files changed

Lines changed: 1407 additions & 6 deletions

‎packages/p/python311/.files‎

812 Bytes
Binary file not shown.

‎packages/p/python311/.rev‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,4 +1933,41 @@ C API
19331933
header files are (bsc#1268375).</comment>
19341934
<requestid>1362558</requestid>
19351935
</revision>
1936+
<revision rev="71" vrev="8">
1937+
<srcmd5>fa1954daf11bcba8d0a648089f70709d</srcmd5>
1938+
<version>3.11.15</version>
1939+
<time>1785601706</time>
1940+
<user>dimstar_suse</user>
1941+
<comment>- CVE-2026-3276: Fix O(n^2) canonical ordering in
1942+
unicodedata.normalize() (bsc#1267581, gh#python/cpython#149079)
1943+
CVE-2026-3276-On2-unicodedata-normalize.patch
1944+
1945+
- CVE-2026-7774: tarfile.data_filter: validate written link
1946+
target (bsc#1267821, gh#149486)
1947+
CVE-2026-7774-tarfile-data_filter-symlink.patch
1948+
1949+
- CVE-2026-0864: Normalize all line endings (CR, CRLF, and LF) in
1950+
configparser (bsc#1269066, gh#python/cpython#143927)
1951+
CVE-2026-0864-normalize-LFTAB-configparser.patch
1952+
1953+
- CVE-2026-11972: Make tarfile._Stream.seek break at EOF
1954+
(bsc#1269788, gh#python/cpython#151981)
1955+
CVE-2026-11972-tarfile-Stream-seek-EOF.patch
1956+
1957+
- CVE-2026-4360: Pass filter_function to TarFile._extract_one()
1958+
during .extract() (bsc#1269959, gh#python/cpython#151987)
1959+
CVE-2026-4360-filter_function-TarFile-extractone.patch
1960+
1961+
- CVE-2026-15308: Fix quadratic complexity in incremental parsing
1962+
in HTMLParser (bsc#1271192, gh#python/cpython#153030)
1963+
CVE-2026-15308-HTMLParser-CPU-exhaust.patch
1964+
1965+
- Add patch support-sphinx-9.patch:
1966+
* Support changes required for Sphinx 9.
1967+
1968+
- CVE-2026-7210: Use XML_SetHashSalt16Bytes in
1969+
pyexpat/_elementtree when possible (bsc#1264962,
1970+
gh#python/cpython#149018).</comment>
1971+
<requestid>1368927</requestid>
1972+
</revision>
19361973
</revisionlist>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
From 6c29d2ac15ca8848cd60c68bd36e592cfc028fda Mon Sep 17 00:00:00 2001
2+
From: Seth Larson <seth@python.org>
3+
Date: Tue, 23 Jun 2026 08:33:51 -0500
4+
Subject: [PATCH] gh-143927: Normalize all line endings (CR, CRLF, and LF) in
5+
configparser (GH-143929) (cherry picked from commit
6+
5858e42c539dac8394636a6e9b30472b8994851f)
7+
8+
Co-authored-by: Seth Larson <seth@python.org>
9+
---
10+
Lib/configparser.py | 4 ++-
11+
Lib/test/test_configparser.py | 11 ++++++++++
12+
Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst | 2 +
13+
3 files changed, 16 insertions(+), 1 deletion(-)
14+
create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
15+
16+
Index: Python-3.11.15/Lib/configparser.py
17+
===================================================================
18+
--- Python-3.11.15.orig/Lib/configparser.py 2026-07-31 16:24:24.390829900 +0200
19+
+++ Python-3.11.15/Lib/configparser.py 2026-07-31 16:24:30.769723267 +0200
20+
@@ -945,7 +945,9 @@
21+
value = self._interpolation.before_write(self, section_name, key,
22+
value)
23+
if value is not None or not self._allow_no_value:
24+
- value = delimiter + str(value).replace('\n', '\n\t')
25+
+ # Convert all possible line-endings into '\n\t'
26+
+ value = (delimiter + str(value).replace('\r\n', '\n')
27+
+ .replace('\r', '\n').replace('\n', '\n\t'))
28+
else:
29+
value = ""
30+
fp.write("{}{}\n".format(key, value))
31+
Index: Python-3.11.15/Lib/test/test_configparser.py
32+
===================================================================
33+
--- Python-3.11.15.orig/Lib/test/test_configparser.py 2026-07-31 16:24:26.518618369 +0200
34+
+++ Python-3.11.15/Lib/test/test_configparser.py 2026-07-31 16:24:30.770159190 +0200
35+
@@ -528,6 +528,17 @@
36+
cf.get(self.default_section, "Foo"), "Bar",
37+
"could not locate option, expecting case-insensitive defaults")
38+
39+
+ def test_crlf_normalization(self):
40+
+ cf = self.newconfig({"key1": "a\nb","key2": "a\rb", "key3": "a\r\nb", "key4": "a\r\nb"})
41+
+ buf = io.StringIO()
42+
+ cf.write(buf)
43+
+ cf_str = buf.getvalue()
44+
+ self.assertNotIn("\r", cf_str)
45+
+ self.assertNotIn("\r\n", cf_str)
46+
+ self.assertEqual(cf_str.count("\n"), 10)
47+
+ self.assertEqual(cf_str.count("\n\t"), 4)
48+
+ self.assertTrue(cf_str.endswith("\n\n"))
49+
+
50+
def test_parse_errors(self):
51+
cf = self.newconfig()
52+
self.parse_error(cf, configparser.ParsingError,
53+
Index: Python-3.11.15/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst
54+
===================================================================
55+
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
56+
+++ Python-3.11.15/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst 2026-07-31 16:24:30.770453896 +0200
57+
@@ -0,0 +1,2 @@
58+
+Normalize all line endings (CR, CRLF, and LF) to LF+TAB when writing
59+
+multi-line configparser values.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
From 8947727186fff1897329325d690af832a5d38e4a Mon Sep 17 00:00:00 2001
2+
From: Stan Ulbrych <stan@python.org>
3+
Date: Tue, 23 Jun 2026 14:31:38 +0100
4+
Subject: [PATCH 1/2] gh-151558: Fix symlink escape via `tarfile`
5+
hardlink-extraction fallback (GH-151559) (cherry picked from commit
6+
27dd970bf6b17ebca7c8ed486a40ab043ed7af8f)
7+
8+
Co-authored-by: Stan Ulbrych <stan@python.org>
9+
---
10+
Lib/tarfile.py | 3 +
11+
Lib/test/test_tarfile.py | 24 ++++++++++
12+
Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst | 3 +
13+
3 files changed, 30 insertions(+)
14+
create mode 100644 Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst
15+
16+
Index: Python-3.11.15/Lib/tarfile.py
17+
===================================================================
18+
--- Python-3.11.15.orig/Lib/tarfile.py 2026-07-02 12:09:33.732145754 +0200
19+
+++ Python-3.11.15/Lib/tarfile.py 2026-07-02 12:09:33.864108270 +0200
20+
@@ -2662,6 +2662,9 @@
21+
"makelink_with_filter: if filter_function is not None, "
22+
+ "extraction_root must also not be None")
23+
try:
24+
+ filter_function(
25+
+ unfiltered.replace(name=tarinfo.name, deep=False),
26+
+ extraction_root)
27+
filtered = filter_function(unfiltered, extraction_root)
28+
except _FILTER_ERRORS as cause:
29+
raise LinkFallbackError(tarinfo, unfiltered.name) from cause
30+
Index: Python-3.11.15/Lib/test/test_tarfile.py
31+
===================================================================
32+
--- Python-3.11.15.orig/Lib/test/test_tarfile.py 2026-07-02 12:09:33.733105801 +0200
33+
+++ Python-3.11.15/Lib/test/test_tarfile.py 2026-07-02 12:09:33.864793876 +0200
34+
@@ -3999,6 +3999,30 @@
35+
self.expect_file("c", symlink_to='b')
36+
37+
@symlink_test
38+
+ def test_sneaky_hardlink_fallback_deep(self):
39+
+ # (CVE-2026-11940)
40+
+ with ArchiveMaker() as arc:
41+
+ arc.add("a/b/s", symlink_to=os.path.join("..", "escape"))
42+
+ arc.add("s", hardlink_to=os.path.join("a", "b", "s"))
43+
+
44+
+ with self.check_context(arc.open(), 'data'):
45+
+ e = self.expect_exception(
46+
+ tarfile.LinkFallbackError,
47+
+ "link 's' would be extracted as a copy of "
48+
+ + "'a/b/s', which was rejected")
49+
+ self.assertIsInstance(e.__cause__,
50+
+ tarfile.LinkOutsideDestinationError)
51+
+
52+
+ for filter in 'tar', 'fully_trusted':
53+
+ with self.subTest(filter), self.check_context(arc.open(), filter):
54+
+ if not os_helper.can_symlink():
55+
+ self.expect_file("a/")
56+
+ self.expect_file("a/b/")
57+
+ else:
58+
+ self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
59+
+ self.expect_file("s", symlink_to=os.path.join('..', 'escape'))
60+
+
61+
+ @symlink_test
62+
def test_exfiltration_via_symlink(self):
63+
# (CVE-2025-4138)
64+
# Test changing symlinks that result in a symlink pointing outside
65+
Index: Python-3.11.15/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst
66+
===================================================================
67+
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
68+
+++ Python-3.11.15/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst 2026-07-02 12:09:33.865970015 +0200
69+
@@ -0,0 +1,3 @@
70+
+Fixed an vulnerability in the :mod:`tarfile` ``data`` and ``tar`` extraction
71+
+filters where crafted archives could create a symlink pointing outside the
72+
+destination directory. This was a bypass of CVE-2025-4330.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
From 3a426f061dd7eeb352b8e71744329507dbddc56a Mon Sep 17 00:00:00 2001
2+
From: Petr Viktorin <encukou@gmail.com>
3+
Date: Tue, 23 Jun 2026 15:13:30 +0200
4+
Subject: [PATCH] gh-151981: Make tarfile._Stream.seek break at EOF (GH-151982)
5+
(cherry picked from commit f50bf13566189c8d0ce5a814f33eff3d89951896)
6+
7+
Co-authored-by: Petr Viktorin <encukou@gmail.com>
8+
Co-authored-by: Stan Ulbrych <stan@python.org>
9+
---
10+
Lib/tarfile.py | 4 +-
11+
Lib/test/test_tarfile.py | 16 ++++++++++
12+
Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst | 2 +
13+
3 files changed, 21 insertions(+), 1 deletion(-)
14+
create mode 100644 Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst
15+
16+
Index: Python-3.11.15/Lib/tarfile.py
17+
===================================================================
18+
--- Python-3.11.15.orig/Lib/tarfile.py 2026-07-31 16:43:26.804350897 +0200
19+
+++ Python-3.11.15/Lib/tarfile.py 2026-07-31 16:43:26.822851734 +0200
20+
@@ -514,7 +514,9 @@
21+
if pos - self.pos >= 0:
22+
blocks, remainder = divmod(pos - self.pos, self.bufsize)
23+
for i in range(blocks):
24+
- self.read(self.bufsize)
25+
+ data = self.read(self.bufsize)
26+
+ if not data:
27+
+ break
28+
self.read(remainder)
29+
else:
30+
raise StreamError("seeking backwards is not allowed")
31+
Index: Python-3.11.15/Lib/test/test_tarfile.py
32+
===================================================================
33+
--- Python-3.11.15.orig/Lib/test/test_tarfile.py 2026-07-31 16:43:26.806165745 +0200
34+
+++ Python-3.11.15/Lib/test/test_tarfile.py 2026-07-31 16:43:26.823763174 +0200
35+
@@ -4489,6 +4489,22 @@
36+
with self.check_context(arc.open(errorlevel='boo!'), filtererror_filter):
37+
self.expect_exception(TypeError) # errorlevel is not int
38+
39+
+ @support.subTests('format', [tarfile.GNU_FORMAT, tarfile.PAX_FORMAT])
40+
+ def test_getmembers_big_size(self, format):
41+
+ # gh-151981: A loop in seek() for streaming files tried to read the
42+
+ # declared number of blocks even at EOF
43+
+ tinfo = tarfile.TarInfo("huge-file")
44+
+ tinfo.size = 1 << 64
45+
+ bio = io.BytesIO()
46+
+ # Write header without data
47+
+ bio.write(tinfo.tobuf(format))
48+
+
49+
+ # Reset & try to get contents
50+
+ bio.seek(0)
51+
+ with tarfile.open(fileobj=bio, mode="r|") as tar:
52+
+ with self.assertRaises(tarfile.ReadError):
53+
+ tar.getmembers()
54+
+
55+
56+
class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase):
57+
testdir = os.path.join(TEMPDIR, "testoverwrite")
58+
Index: Python-3.11.15/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst
59+
===================================================================
60+
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
61+
+++ Python-3.11.15/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst 2026-07-31 16:43:26.824332719 +0200
62+
@@ -0,0 +1,2 @@
63+
+In :mod:`tarfile`, seeking a stream now stops when end of the stream is
64+
+reached.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
From e7b8f1050a540fdbe8771db94ad998ce7a5540d0 Mon Sep 17 00:00:00 2001
2+
From: Serhiy Storchaka <storchaka@gmail.com>
3+
Date: Sat, 4 Jul 2026 20:40:22 +0300
4+
Subject: [PATCH] gh-153030: Fix quadratic complexity in incremental parsing in
5+
HTMLParser (GH-153031)
6+
7+
When an unterminated construct (e.g. a tag or comment) spanned many
8+
feed() calls, rescanning the growing buffer and concatenating new data
9+
onto it were both quadratic. New data is now accumulated in a list and
10+
only joined and parsed once enough has piled up.
11+
12+
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13+
(cherry picked from commit bcf98ddbc40ec9b3ee87da0124a5660b19b7e606)
14+
---
15+
Lib/html/parser.py | 32 +++++++++-
16+
Lib/test/test_htmlparser.py | 20 ++++++
17+
Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst | 3
18+
3 files changed, 53 insertions(+), 2 deletions(-)
19+
create mode 100644 Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst
20+
21+
Index: Python-3.11.15/Lib/html/parser.py
22+
===================================================================
23+
--- Python-3.11.15.orig/Lib/html/parser.py 2026-07-31 16:17:58.516657311 +0200
24+
+++ Python-3.11.15/Lib/html/parser.py 2026-07-31 16:18:04.726530944 +0200
25+
@@ -137,6 +137,9 @@
26+
self.cdata_elem = None
27+
self._support_cdata = True
28+
self._escapable = True
29+
+ self._pending = []
30+
+ self._pending_len = 0
31+
+ self._parse_threshold = 1
32+
_markupbase.ParserBase.reset(self)
33+
34+
def feed(self, data):
35+
@@ -145,11 +148,36 @@
36+
Call this as often as you want, with as little or as much text
37+
as you want (may include '\n').
38+
"""
39+
- self.rawdata = self.rawdata + data
40+
- self.goahead(0)
41+
+ # Accumulate new data in a list and only join and parse it once
42+
+ # enough has piled up. Rescanning an unparsed buffer (e.g. an
43+
+ # unterminated tag) and concatenating onto it on every call would
44+
+ # both be quadratic in the input size.
45+
+ self._pending_len += len(data)
46+
+ if self._pending_len < self._parse_threshold:
47+
+ self._pending.append(data)
48+
+ else:
49+
+ if not self._pending:
50+
+ self.rawdata += data
51+
+ else:
52+
+ self._pending.append(data)
53+
+ self.rawdata += ''.join(self._pending)
54+
+ self._pending.clear()
55+
+ self._pending_len = 0
56+
+ n = len(self.rawdata)
57+
+ self.goahead(0)
58+
+ if len(self.rawdata) < n:
59+
+ # Some data was parsed; resume on the next call.
60+
+ self._parse_threshold = 1
61+
+ else:
62+
+ # Nothing was parsed; wait until the buffer doubles.
63+
+ self._parse_threshold = len(self.rawdata)
64+
65+
def close(self):
66+
"""Handle any buffered data."""
67+
+ if self._pending:
68+
+ self.rawdata += ''.join(self._pending)
69+
+ self._pending.clear()
70+
+ self._pending_len = 0
71+
self.goahead(1)
72+
73+
__starttag_text = None
74+
Index: Python-3.11.15/Lib/test/test_htmlparser.py
75+
===================================================================
76+
--- Python-3.11.15.orig/Lib/test/test_htmlparser.py 2026-07-31 16:18:00.172812370 +0200
77+
+++ Python-3.11.15/Lib/test/test_htmlparser.py 2026-07-31 16:18:04.727730991 +0200
78+
@@ -929,6 +929,26 @@
79+
check("<![CDATA[" * 9 * n)
80+
check("<!doctype" * 35 * n)
81+
82+
+ @support.requires_resource('cpu')
83+
+ def test_incremental_no_quadratic_complexity(self):
84+
+ # An unterminated construct fed in many small chunks used to take
85+
+ # quadratic time, both to rescan and to concatenate the buffer.
86+
+ # Now it takes a fraction of a second.
87+
+ def check(prefix, chunk, suffix):
88+
+ parser = html.parser.HTMLParser()
89+
+ parser.feed(prefix)
90+
+ for _ in range(200_000):
91+
+ parser.feed(chunk)
92+
+ parser.feed(suffix)
93+
+ parser.close()
94+
+ chunk = "a" * 64
95+
+ check("<!--", chunk, "-->") # comment
96+
+ check("<?", chunk, ">") # processing instruction
97+
+ check("<!doctype ", chunk, ">") # doctype
98+
+ check("<![CDATA[", chunk, "]]>") # CDATA section
99+
+ check("<a href='", chunk, "'>") # start tag
100+
+ check("<script>", chunk, "</script>") # RAWTEXT element
101+
+
102+
103+
class AttributesTestCase(TestCaseBase):
104+
105+
Index: Python-3.11.15/Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst
106+
===================================================================
107+
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
108+
+++ Python-3.11.15/Misc/NEWS.d/next/Security/2026-07-04-17-00-00.gh-issue-153030.RovkP6.rst 2026-07-31 16:18:04.727885463 +0200
109+
@@ -0,0 +1,3 @@
110+
+Fixed quadratic complexity in incremental parsing of long unterminated
111+
+constructs (such as tags or comments) in :class:`html.parser.HTMLParser`,
112+
+which could be exploited for a denial of service.

0 commit comments

Comments
 (0)