diff --git a/SPECS/python3/CVE-2026-0864.patch b/SPECS/python3/CVE-2026-0864.patch new file mode 100644 index 00000000000..0be5331126a --- /dev/null +++ b/SPECS/python3/CVE-2026-0864.patch @@ -0,0 +1,68 @@ +From db4a157c790479710a1a840d7937c5c815a6f8b6 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:27:20 +0200 +Subject: [PATCH] [3.12] gh-143927: Normalize all line endings (CR, CRLF, and + LF) in configparser (GH-143929) (#152005) + +gh-143927: Normalize all line endings (CR, CRLF, and LF) in configparser (GH-143929) +(cherry picked from commit 5858e42c539dac8394636a6e9b30472b8994851f) + +Co-authored-by: Seth Larson + +Upstream Patch Reference: https://github.com/python/cpython/commit/db4a157c790479710a1a840d7937c5c815a6f8b6.patch +--- + Lib/configparser.py | 4 +++- + Lib/test/test_configparser.py | 11 +++++++++++ + .../2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst | 2 ++ + 3 files changed, 16 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst + +diff --git a/Lib/configparser.py b/Lib/configparser.py +index f96704e..8ae35a0 100644 +--- a/Lib/configparser.py ++++ b/Lib/configparser.py +@@ -907,7 +907,9 @@ class RawConfigParser(MutableMapping): + value = self._interpolation.before_write(self, section_name, key, + value) + if value is not None or not self._allow_no_value: +- value = delimiter + str(value).replace('\n', '\n\t') ++ # Convert all possible line-endings into '\n\t' ++ value = (delimiter + str(value).replace('\r\n', '\n') ++ .replace('\r', '\n').replace('\n', '\n\t')) + else: + value = "" + fp.write("{}{}\n".format(key, value)) +diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py +index b7e68d7..389aa15 100644 +--- a/Lib/test/test_configparser.py ++++ b/Lib/test/test_configparser.py +@@ -527,6 +527,17 @@ boolean {0[0]} NO + cf.get(self.default_section, "Foo"), "Bar", + "could not locate option, expecting case-insensitive defaults") + ++ def test_crlf_normalization(self): ++ cf = self.newconfig({"key1": "a\nb","key2": "a\rb", "key3": "a\r\nb", "key4": "a\r\nb"}) ++ buf = io.StringIO() ++ cf.write(buf) ++ cf_str = buf.getvalue() ++ self.assertNotIn("\r", cf_str) ++ self.assertNotIn("\r\n", cf_str) ++ self.assertEqual(cf_str.count("\n"), 10) ++ self.assertEqual(cf_str.count("\n\t"), 4) ++ self.assertTrue(cf_str.endswith("\n\n")) ++ + def test_parse_errors(self): + cf = self.newconfig() + self.parse_error(cf, configparser.ParsingError, +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst +new file mode 100644 +index 0000000..ca55499 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-58-19.gh-issue-143927.aviFeG.rst +@@ -0,0 +1,2 @@ ++Normalize all line endings (CR, CRLF, and LF) to LF+TAB when writing ++multi-line configparser values. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-11972.patch b/SPECS/python3/CVE-2026-11972.patch new file mode 100644 index 00000000000..9249e712479 --- /dev/null +++ b/SPECS/python3/CVE-2026-11972.patch @@ -0,0 +1,111 @@ +From 26ce07f59a656b5da92114392a2dc27267de9274 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:26:39 +0200 +Subject: [PATCH] gh-151981: Make tarfile._Stream.seek break at EOF (GH-151982) + (#151994) + +gh-151981: Make tarfile._Stream.seek break at EOF (GH-151982) +(cherry picked from commit f50bf13566189c8d0ce5a814f33eff3d89951896) + +Co-authored-by: Petr Viktorin +Co-authored-by: Stan Ulbrych +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/f5e2776ff0383a902c12acf2b703e7e951fc8438.patch +--- + Lib/tarfile.py | 4 ++- + Lib/test/support/__init__.py | 25 +++++++++++++++++++ + Lib/test/test_tarfile.py | 16 ++++++++++++ + ...-06-23-13-28-16.gh-issue-151981.xBHEcU.rst | 2 ++ + 4 files changed, 46 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst + +diff --git a/Lib/tarfile.py b/Lib/tarfile.py +index 461ec16..28c3edf 100755 +--- a/Lib/tarfile.py ++++ b/Lib/tarfile.py +@@ -516,7 +516,9 @@ class _Stream: + if pos - self.pos >= 0: + blocks, remainder = divmod(pos - self.pos, self.bufsize) + for i in range(blocks): +- self.read(self.bufsize) ++ data = self.read(self.bufsize) ++ if not data: ++ break + self.read(remainder) + else: + raise StreamError("seeking backwards is not allowed") +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index abd2fea..4ccce74 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -863,6 +863,31 @@ def check_sizeof(test, o, size): + % (type(o), result, size) + test.assertEqual(result, size, msg) + ++def subTests(arg_names, arg_values, /, *, _do_cleanups=False): ++ """Run multiple subtests with different parameters. ++ """ ++ single_param = False ++ if isinstance(arg_names, str): ++ arg_names = arg_names.replace(',',' ').split() ++ if len(arg_names) == 1: ++ single_param = True ++ arg_values = tuple(arg_values) ++ def decorator(func): ++ if isinstance(func, type): ++ raise TypeError('subTests() can only decorate methods, not classes') ++ @functools.wraps(func) ++ def wrapper(self, /, *args, **kwargs): ++ for values in arg_values: ++ if single_param: ++ values = (values,) ++ subtest_kwargs = dict(zip(arg_names, values)) ++ with self.subTest(**subtest_kwargs): ++ func(self, *args, **kwargs, **subtest_kwargs) ++ if _do_cleanups: ++ self.doCleanups() ++ return wrapper ++ return decorator ++ + #======================================================================= + # Decorator/context manager for running a code in a different locale, + # correctly resetting it afterwards. +diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py +index 717d1c1..8388dc4 100644 +--- a/Lib/test/test_tarfile.py ++++ b/Lib/test/test_tarfile.py +@@ -4555,6 +4555,22 @@ class TestExtractionFilters(unittest.TestCase): + with self.check_context(arc.open(errorlevel='boo!'), filtererror_filter): + self.expect_exception(TypeError) # errorlevel is not int + ++ @support.subTests('format', [tarfile.GNU_FORMAT, tarfile.PAX_FORMAT]) ++ def test_getmembers_big_size(self, format): ++ # gh-151981: A loop in seek() for streaming files tried to read the ++ # declared number of blocks even at EOF ++ tinfo = tarfile.TarInfo("huge-file") ++ tinfo.size = 1 << 64 ++ bio = io.BytesIO() ++ # Write header without data ++ bio.write(tinfo.tobuf(format)) ++ ++ # Reset & try to get contents ++ bio.seek(0) ++ with tarfile.open(fileobj=bio, mode="r|") as tar: ++ with self.assertRaises(tarfile.ReadError): ++ tar.getmembers() ++ + + class OverwriteTests(archiver_tests.OverwriteTests, unittest.TestCase): + testdir = os.path.join(TEMPDIR, "testoverwrite") +diff --git a/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst b/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst +new file mode 100644 +index 0000000..2123ab8 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-06-23-13-28-16.gh-issue-151981.xBHEcU.rst +@@ -0,0 +1,2 @@ ++In :mod:`tarfile`, seeking a stream now stops when end of the stream is ++reached. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-12003.patch b/SPECS/python3/CVE-2026-12003.patch new file mode 100644 index 00000000000..800ad69f186 --- /dev/null +++ b/SPECS/python3/CVE-2026-12003.patch @@ -0,0 +1,94 @@ +From d5ee580a170b7afe7dda8d5a89102ed5cc1ac683 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:24:46 +0200 +Subject: [PATCH] gh-151544: Fixes CVE-2026-12003 by removing the fallback to + %VPATH%/Modules/Setup.local for discovering sources in getpath.py (GH-151545) + (#151567) + +gh-151544: Fixes CVE-2026-12003 by removing the fallback to %VPATH%/Modules/Setup.local for discovering sources in getpath.py (GH-151545) +(cherry picked from commit 9e863fab283eddca9c2a8f9d1ee30f4dc243e314) + +Co-authored-by: Steve Dower +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/03ab7b44788bfd6b8927e16bcdbd025aa08dce06.patch +--- + Makefile.pre.in | 2 ++ + ...2026-06-16-14-58-02.gh-issue-151544._bexVy.rst | 4 ++++ + Modules/getpath.py | 15 ++++----------- + 3 files changed, 10 insertions(+), 11 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst + +diff --git a/Makefile.pre.in b/Makefile.pre.in +index 689f33d..8d13d20 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -1074,6 +1074,8 @@ Programs/_bootstrap_python.o: Programs/_bootstrap_python.c $(BOOTSTRAP_HEADERS) + _bootstrap_python: $(LIBRARY_OBJS_OMIT_FROZEN) Programs/_bootstrap_python.o Modules/getpath.o Modules/Setup.local + $(LINKCC) $(PY_LDFLAGS_NOLTO) -o $@ $(LIBRARY_OBJS_OMIT_FROZEN) \ + Programs/_bootstrap_python.o Modules/getpath.o $(LIBS) $(MODLIBS) $(SYSLIBS) ++ # Dummy pybuilddir.txt is needed for _bootstrap_python to be runnable ++ @echo "none" > ./pybuilddir.txt + + + ############################################################################ +diff --git a/Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst b/Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst +new file mode 100644 +index 0000000..418e3b4 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-06-16-14-58-02.gh-issue-151544._bexVy.rst +@@ -0,0 +1,4 @@ ++:file:`Modules/Setup.local` is no longer used as a landmark to discover ++whether Python is running in a source tree, as it could potentially affect ++actual installs. The :file:`pybuilddir.txt` file is now the sole indicator ++of running in a source tree. +diff --git a/Modules/getpath.py b/Modules/getpath.py +index 9913fcb..29a6b30 100644 +--- a/Modules/getpath.py ++++ b/Modules/getpath.py +@@ -128,8 +128,7 @@ + # checked by looking for the BUILDDIR_TXT file, which contains the + # relative path to the platlib dir. The executable_dir value is + # derived from joining the VPATH preprocessor variable to the +-# directory containing pybuilddir.txt. If it is not found, the +-# BUILD_LANDMARK file is found, which is part of the source tree. ++# directory containing pybuilddir.txt. + # prefix is then found by searching up for a file that should only + # exist in the source tree, and the stdlib dir is set to prefix/Lib. + +@@ -175,7 +174,6 @@ platlibdir = config.get('platlibdir') or PLATLIBDIR + + if os_name == 'posix' or os_name == 'darwin': + BUILDDIR_TXT = 'pybuilddir.txt' +- BUILD_LANDMARK = 'Modules/Setup.local' + DEFAULT_PROGRAM_NAME = f'python{VERSION_MAJOR}' + STDLIB_SUBDIR = f'{platlibdir}/python{VERSION_MAJOR}.{VERSION_MINOR}' + STDLIB_LANDMARKS = [f'{STDLIB_SUBDIR}/os.py', f'{STDLIB_SUBDIR}/os.pyc'] +@@ -188,7 +186,6 @@ if os_name == 'posix' or os_name == 'darwin': + + elif os_name == 'nt': + BUILDDIR_TXT = 'pybuilddir.txt' +- BUILD_LANDMARK = f'{VPATH}\\Modules\\Setup.local' + DEFAULT_PROGRAM_NAME = f'python' + STDLIB_SUBDIR = 'Lib' + STDLIB_LANDMARKS = [f'{STDLIB_SUBDIR}\\os.py', f'{STDLIB_SUBDIR}\\os.pyc'] +@@ -495,13 +492,9 @@ if ((not home_was_set and real_executable_dir and not py_setpath) + platstdlib_dir = real_executable_dir + build_prefix = joinpath(real_executable_dir, VPATH) + except (FileNotFoundError, PermissionError): +- if isfile(joinpath(real_executable_dir, BUILD_LANDMARK)): +- build_prefix = joinpath(real_executable_dir, VPATH) +- if os_name == 'nt': +- # QUIRK: Windows builds need platstdlib_dir to be the executable +- # dir. Normally the builddir marker handles this, but in this +- # case we need to correct manually. +- platstdlib_dir = real_executable_dir ++ # We used to check for an alternate landmark here, but now we require ++ # BUILDDIR_TXT to exist. (gh-151544; CVE-2026-12003) ++ pass + + if build_prefix: + if os_name == 'nt': +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-2297.patch b/SPECS/python3/CVE-2026-2297.patch new file mode 100644 index 00000000000..35e8eafa20f --- /dev/null +++ b/SPECS/python3/CVE-2026-2297.patch @@ -0,0 +1,43 @@ +From d3a9b6792366a8386be7278540b6e3e7037413ae Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:14:48 +0200 +Subject: [PATCH] gh-145506: Fixes CVE-2026-2297 by ensuring + SourcelessFileLoader uses io.open_code (GH-145507) (#145514) + +gh-145506: Fixes CVE-2026-2297 by ensuring SourcelessFileLoader uses io.open_code (GH-145507) +(cherry picked from commit a51b1b512de1d56b3714b65628a2eae2b07e535e) + +Co-authored-by: Steve Dower +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/c70adad78caeeea33f92f560ecb93331ca11bf66.patch +--- + Lib/importlib/_bootstrap_external.py | 2 +- + .../Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst | 2 ++ + 2 files changed, 3 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst + +diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py +index 9b8a8df..6e4a087 100644 +--- a/Lib/importlib/_bootstrap_external.py ++++ b/Lib/importlib/_bootstrap_external.py +@@ -1186,7 +1186,7 @@ class FileLoader: + + def get_data(self, path): + """Return the data from path as raw bytes.""" +- if isinstance(self, (SourceLoader, ExtensionFileLoader)): ++ if isinstance(self, (SourceLoader, SourcelessFileLoader, ExtensionFileLoader)): + with _io.open_code(str(path)) as file: + return file.read() + else: +diff --git a/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst b/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst +new file mode 100644 +index 0000000..dcdb44d +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst +@@ -0,0 +1,2 @@ ++Fixes :cve:`2026-2297` by ensuring that ``SourcelessFileLoader`` uses ++:func:`io.open_code` when opening ``.pyc`` files. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-3087.patch b/SPECS/python3/CVE-2026-3087.patch new file mode 100644 index 00000000000..71400101b3f --- /dev/null +++ b/SPECS/python3/CVE-2026-3087.patch @@ -0,0 +1,210 @@ +From 0f828887a986dee09b395bbd761c250a53c56b37 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:20:47 +0200 +Subject: [PATCH] gh-146581: Fix vulnerability in shutil.unpack_archive() for + ZIP files on Windows (GH-146591) (#149066) + +gh-146581: Fix vulnerability in shutil.unpack_archive() for ZIP files on Windows (GH-146591) + +Use ZipFile.extractall() to sanitize file names and extract files. + +Files with invalid names (e.g. absolute paths) are now skipped. + +Files containing ".." in the name are no longer skipped. +(cherry picked from commit fc829e88753858c8ac669594bf0093f44948c0f4) + +Co-authored-by: Serhiy Storchaka +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/a6650a2cdf0c49fb8ce0c982903aa2aa274beefe.patch +--- + Lib/shutil.py | 24 +------ + Lib/test/test_shutil.py | 67 ++++++++++++++++++- + Lib/zipfile/__init__.py | 21 ++++-- + ...-03-29-12-51-33.gh-issue-146581.4vZfB0.rst | 5 ++ + 4 files changed, 89 insertions(+), 28 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst + +diff --git a/Lib/shutil.py b/Lib/shutil.py +index 2d28569..90f6c02 100644 +--- a/Lib/shutil.py ++++ b/Lib/shutil.py +@@ -1237,27 +1237,9 @@ def _unpack_zipfile(filename, extract_dir): + if not zipfile.is_zipfile(filename): + raise ReadError("%s is not a zip file" % filename) + +- zip = zipfile.ZipFile(filename) +- try: +- for info in zip.infolist(): +- name = info.filename +- +- # don't extract absolute paths or ones with .. in them +- if name.startswith('/') or '..' in name: +- continue +- +- targetpath = os.path.join(extract_dir, *name.split('/')) +- if not targetpath: +- continue +- +- _ensure_directory(targetpath) +- if not name.endswith('/'): +- # file +- with zip.open(name, 'r') as source, \ +- open(targetpath, 'wb') as target: +- copyfileobj(source, target) +- finally: +- zip.close() ++ with zipfile.ZipFile(filename) as zip: ++ zip._ignore_invalid_names = True ++ zip.extractall(extract_dir) + + def _unpack_tarfile(filename, extract_dir, *, filter=None): + """Unpack tar/tar.gz/tar.bz2/tar.xz `filename` to `extract_dir` +diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py +index c553ea0..850593c 100644 +--- a/Lib/test/test_shutil.py ++++ b/Lib/test/test_shutil.py +@@ -2059,8 +2059,6 @@ class TestArchives(BaseTest, unittest.TestCase): + def check_unpack_archive(self, format, **kwargs): + self.check_unpack_archive_with_converter( + format, lambda path: path, **kwargs) +- self.check_unpack_archive_with_converter( +- format, FakePath, **kwargs) + self.check_unpack_archive_with_converter(format, FakePath, **kwargs) + + def check_unpack_archive_with_converter(self, format, converter, **kwargs): +@@ -2116,6 +2114,71 @@ class TestArchives(BaseTest, unittest.TestCase): + with self.assertRaises(TypeError): + self.check_unpack_archive('zip', filter='data') + ++ def test_unpack_archive_zip_badpaths(self): ++ srcdir = self.mkdtemp() ++ zipname = os.path.join(srcdir, 'test.zip') ++ abspath = os.path.join(srcdir, 'abspath') ++ with zipfile.ZipFile(zipname, 'w') as zf: ++ zf.writestr(abspath, 'badfile') ++ zf.writestr(os.sep + abspath, 'badfile') ++ zf.writestr('/abspath', 'badfile') ++ zf.writestr('C:/abspath', 'badfile') ++ zf.writestr('D:\\abspath', 'badfile') ++ zf.writestr('E:abspath', 'badfile') ++ zf.writestr('F:/G:/abspath', 'badfile') ++ zf.writestr('//server/share/abspath', 'badfile') ++ zf.writestr('\\\\server2\\share\\abspath', 'badfile') ++ zf.writestr('../relpath', 'badfile') ++ zf.writestr(os.pardir + os.sep + 'relpath2', 'badfile') ++ zf.writestr('good/file', 'goodfile') ++ zf.writestr('good..file', 'goodfile') ++ ++ dstdir = os.path.join(self.mkdtemp(), 'dst') ++ unpack_archive(zipname, dstdir) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'good', 'file'))) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'good..file'))) ++ self.assertFalse(os.path.exists(abspath)) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, 'abspath'))) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, 'G_'))) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, 'server'))) ++ if os.name != 'nt': ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'C:', 'abspath'))) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'D:\\abspath'))) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'E:abspath'))) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, 'F:', 'G:', 'abspath'))) ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, '\\\\server2\\share\\abspath'))) ++ if os.pardir == '..': ++ self.assertFalse(os.path.exists(os.path.join(dstdir, '..', 'relpath'))) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, 'relpath'))) ++ else: ++ self.assertTrue(os.path.isfile(os.path.join(dstdir, '..', 'relpath'))) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, os.pardir, 'relpath2'))) ++ self.assertFalse(os.path.exists(os.path.join(dstdir, 'relpath2'))) ++ ++ dstdir2 = os.path.join(self.mkdtemp(), 'dst') ++ os.mkdir(dstdir2) ++ with os_helper.change_cwd(dstdir2): ++ unpack_archive(zipname, '') ++ self.assertTrue(os.path.isfile(os.path.join('good', 'file'))) ++ self.assertTrue(os.path.isfile('good..file')) ++ self.assertFalse(os.path.exists(abspath)) ++ self.assertFalse(os.path.exists('abspath')) ++ self.assertFalse(os.path.exists('C_')) ++ self.assertFalse(os.path.exists('server')) ++ if os.name != 'nt': ++ self.assertTrue(os.path.isfile(os.path.join('C:', 'abspath'))) ++ self.assertTrue(os.path.isfile('D:\\abspath')) ++ self.assertTrue(os.path.isfile('E:abspath')) ++ self.assertTrue(os.path.isfile(os.path.join('F:', 'G:', 'abspath'))) ++ self.assertTrue(os.path.isfile('\\\\server2\\share\\abspath')) ++ if os.pardir == '..': ++ self.assertFalse(os.path.exists(os.path.join('..', 'relpath'))) ++ self.assertFalse(os.path.exists('relpath')) ++ else: ++ self.assertTrue(os.path.isfile(os.path.join('..', 'relpath'))) ++ self.assertFalse(os.path.exists(os.path.join(os.pardir, 'relpath2'))) ++ self.assertFalse(os.path.exists('relpath2')) ++ + def test_unpack_registry(self): + + formats = get_unpack_formats() +diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py +index aa9750e..3ae6b37 100644 +--- a/Lib/zipfile/__init__.py ++++ b/Lib/zipfile/__init__.py +@@ -1309,6 +1309,7 @@ class ZipFile: + + fp = None # Set here since __del__ checks it + _windows_illegal_name_trans_table = None ++ _ignore_invalid_names = False + + def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True, + compresslevel=None, *, strict_timestamps=True, metadata_encoding=None): +@@ -1785,21 +1786,31 @@ class ZipFile: + + # build the destination pathname, replacing + # forward slashes to platform specific separators. +- arcname = member.filename.replace('/', os.path.sep) +- +- if os.path.altsep: ++ arcname = member.filename ++ if os.path.sep != '/': ++ arcname = arcname.replace('/', os.path.sep) ++ if os.path.altsep and os.path.altsep != '/': + arcname = arcname.replace(os.path.altsep, os.path.sep) + # interpret absolute pathname as relative, remove drive letter or + # UNC path, redundant separators, "." and ".." components. +- arcname = os.path.splitdrive(arcname)[1] ++ drive, root, arcname = os.path.splitroot(arcname) ++ if self._ignore_invalid_names and (drive or root): ++ return None ++ if self._ignore_invalid_names and os.path.pardir in arcname.split(os.path.sep): ++ return None + invalid_path_parts = ('', os.path.curdir, os.path.pardir) + arcname = os.path.sep.join(x for x in arcname.split(os.path.sep) + if x not in invalid_path_parts) + if os.path.sep == '\\': + # filter illegal characters on Windows +- arcname = self._sanitize_windows_name(arcname, os.path.sep) ++ arcname2 = self._sanitize_windows_name(arcname, os.path.sep) ++ if self._ignore_invalid_names and arcname2 != arcname: ++ return None ++ arcname = arcname2 + + if not arcname and not member.is_dir(): ++ if self._ignore_invalid_names: ++ return None + raise ValueError("Empty filename.") + + targetpath = os.path.join(targetpath, arcname) +diff --git a/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst b/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst +new file mode 100644 +index 0000000..98e6554 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-03-29-12-51-33.gh-issue-146581.4vZfB0.rst +@@ -0,0 +1,5 @@ ++Fix vulnerability in :func:`shutil.unpack_archive` for ZIP files on Windows ++which allowed to write files outside of the destination tree if the patch in ++the archive contains a Windows drive prefix. Now such invalid paths will be ++skipped. Files containing ".." in the name (like "foo..bar") are no longer ++skipped. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-3276.patch b/SPECS/python3/CVE-2026-3276.patch new file mode 100644 index 00000000000..7c44165f638 --- /dev/null +++ b/SPECS/python3/CVE-2026-3276.patch @@ -0,0 +1,276 @@ +From 5d6b90e982bb6f515e1483a480b15a8e462ca1e4 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Tue, 4 Aug 2026 11:22:11 +0200 +Subject: [PATCH] gh-149079: Fix O(n^2) canonical ordering in + unicodedata.normalize() (GH-149080) (#150843) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Replace the insertion sort used for canonical ordering of combining +characters with a hybrid approach: insertion sort for short runs (< 20) +and counting sort for longer runs, reducing worst-case complexity from +O(n^2) to O(n). This prevents denial of service via crafted Unicode +strings with many combining characters in alternating CCC order. + +(cherry picked from commit 991224b1e8311c85f198f6dd8208bf8cff7fc26f) + +Co-authored-by: Seth Larson +Co-authored-by: ch4n3-yoon +Co-authored-by: Seokchan Yoon <13852925+ch4n3-yoon@users.noreply.github.com> +Co-authored-by: Stan Ulbrych +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> +Co-authored-by: Serhiy Storchaka +Co-authored-by: Maurycy Pawłowski-Wieroński +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/d3ab945af25b28dfe13ac6cb40c124a01b33ce1f.patch +--- + Lib/test/test_unicodedata.py | 28 ++++ + ...-04-27-16-36-11.gh-issue-149079.vKl-LM.rst | 5 + + Modules/unicodedata.c | 144 ++++++++++++++---- + 3 files changed, 151 insertions(+), 26 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst + +diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py +index 515c384..6b4bff1 100644 +--- a/Lib/test/test_unicodedata.py ++++ b/Lib/test/test_unicodedata.py +@@ -203,6 +203,34 @@ class UnicodeFunctionsTest(UnicodeDatabaseTest): + b = 'C\u0338' * 20 + '\xC7' + self.assertEqual(self.db.normalize('NFC', a), b) + ++ def test_long_combining_mark_run(self): ++ # gh-149079: avoid quadratic canonical ordering. ++ payload = "a" + ("\u0300\u0327" * 32) ++ nfd = "a" + ("\u0327" * 32) + ("\u0300" * 32) ++ nfc = "\u00e0" + ("\u0327" * 32) + ("\u0300" * 31) ++ ++ self.assertEqual(self.db.normalize("NFD", payload), nfd) ++ self.assertEqual(self.db.normalize("NFKD", payload), nfd) ++ self.assertEqual(self.db.normalize("NFC", payload), nfc) ++ self.assertEqual(self.db.normalize("NFKC", payload), nfc) ++ ++ def test_combining_mark_run_fast_paths(self): ++ # gh-149079: cover short runs and already-sorted long runs. ++ short_payload = "a" + ("\u0300\u0327" * 9) + "\u0300" ++ short_nfd = "a" + ("\u0327" * 9) + ("\u0300" * 10) ++ short_nfc = "\u00e0" + ("\u0327" * 9) + ("\u0300" * 9) ++ long_sorted = "a" + ("\u0327" * 30) + ("\u0300" * 30) ++ long_sorted_nfc = "\u00e0" + ("\u0327" * 30) + ("\u0300" * 29) ++ ++ self.assertEqual(self.db.normalize("NFD", short_payload), short_nfd) ++ self.assertEqual(self.db.normalize("NFKD", short_payload), short_nfd) ++ self.assertEqual(self.db.normalize("NFC", short_payload), short_nfc) ++ self.assertEqual(self.db.normalize("NFKC", short_payload), short_nfc) ++ self.assertEqual(self.db.normalize("NFD", long_sorted), long_sorted) ++ self.assertEqual(self.db.normalize("NFKD", long_sorted), long_sorted) ++ self.assertEqual(self.db.normalize("NFC", long_sorted), long_sorted_nfc) ++ self.assertEqual(self.db.normalize("NFKC", long_sorted), long_sorted_nfc) ++ + def test_issue29456(self): + # Fix #29456 + u1176_str_a = '\u1100\u1176\u11a8' +diff --git a/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst b/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst +new file mode 100644 +index 0000000..4ed22b5 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-04-27-16-36-11.gh-issue-149079.vKl-LM.rst +@@ -0,0 +1,5 @@ ++Fix a potential denial of service in :func:`unicodedata.normalize`. The ++canonical ordering step of Unicode normalization used a quadratic-time insertion ++sort for reordering combining characters, which could be exploited with ++crafted input containing many combining characters in non-canonical order. ++Replaced with a linear-time counting sort for long runs. +diff --git a/Modules/unicodedata.c b/Modules/unicodedata.c +index 41dcd5f..de34516 100644 +--- a/Modules/unicodedata.c ++++ b/Modules/unicodedata.c +@@ -490,19 +490,80 @@ get_decomp_record(PyObject *self, Py_UCS4 code, + #define NCount (VCount*TCount) + #define SCount (LCount*NCount) + ++/* Small combining runs are usually cheaper with insertion sort. */ ++#define CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD 20 ++ ++static void ++canonical_ordering_sort_insertion(int kind, void *data, ++ Py_ssize_t start, Py_ssize_t end) ++{ ++ for (Py_ssize_t i = start + 1; i < end; i++) { ++ Py_UCS4 code = PyUnicode_READ(kind, data, i); ++ unsigned char combining = _getrecord_ex(code)->combining; ++ Py_ssize_t j = i; ++ ++ while (j > start) { ++ Py_UCS4 previous = PyUnicode_READ(kind, data, j - 1); ++ if (_getrecord_ex(previous)->combining <= combining) { ++ break; ++ } ++ PyUnicode_WRITE(kind, data, j, previous); ++ j--; ++ } ++ if (j != i) { ++ PyUnicode_WRITE(kind, data, j, code); ++ } ++ } ++} ++ ++static void ++canonical_ordering_sort_counting(int kind, void *data, ++ Py_ssize_t start, Py_ssize_t end, ++ Py_UCS4 *sortbuf) ++{ ++ Py_ssize_t counts[256] = {0}; ++ Py_ssize_t run_length = end - start; ++ Py_ssize_t total = 0; ++ ++ for (Py_ssize_t i = start; i < end; i++) { ++ Py_UCS4 code = PyUnicode_READ(kind, data, i); ++ unsigned char combining = _getrecord_ex(code)->combining; ++ counts[combining]++; ++ } ++ ++ for (size_t i = 0; i < Py_ARRAY_LENGTH(counts); i++) { ++ Py_ssize_t count = counts[i]; ++ counts[i] = total; ++ total += count; ++ } ++ ++ /* Reuse counts[] as the next output slot for each CCC. */ ++ for (Py_ssize_t i = start; i < end; i++) { ++ Py_UCS4 code = PyUnicode_READ(kind, data, i); ++ unsigned char combining = _getrecord_ex(code)->combining; ++ sortbuf[counts[combining]++] = code; ++ } ++ for (Py_ssize_t i = 0; i < run_length; i++) { ++ PyUnicode_WRITE(kind, data, start + i, sortbuf[i]); ++ } ++} ++ + static PyObject* + nfd_nfkd(PyObject *self, PyObject *input, int k) + { + PyObject *result; + Py_UCS4 *output; + Py_ssize_t i, o, osize; +- int kind; +- const void *data; ++ int input_kind, result_kind; ++ const void *input_data; ++ void *result_data; + /* Longest decomposition in Unicode 3.2: U+FDFA */ + Py_UCS4 stack[20]; + Py_ssize_t space, isize; + int index, prefix, count, stackptr; + unsigned char prev, cur; ++ Py_UCS4 *sortbuf = NULL; ++ Py_ssize_t sortbuflen = 0; + + stackptr = 0; + isize = PyUnicode_GET_LENGTH(input); +@@ -522,11 +583,11 @@ nfd_nfkd(PyObject *self, PyObject *input, int k) + return NULL; + } + i = o = 0; +- kind = PyUnicode_KIND(input); +- data = PyUnicode_DATA(input); ++ input_kind = PyUnicode_KIND(input); ++ input_data = PyUnicode_DATA(input); + + while (i < isize) { +- stack[stackptr++] = PyUnicode_READ(kind, data, i++); ++ stack[stackptr++] = PyUnicode_READ(input_kind, input_data, i++); + while(stackptr) { + Py_UCS4 code = stack[--stackptr]; + /* Hangul Decomposition adds three characters in +@@ -591,35 +652,66 @@ nfd_nfkd(PyObject *self, PyObject *input, int k) + PyMem_Free(output); + if (!result) + return NULL; ++ + /* result is guaranteed to be ready, as it is compact. */ +- kind = PyUnicode_KIND(result); +- data = PyUnicode_DATA(result); ++ result_kind = PyUnicode_KIND(result); ++ result_data = PyUnicode_DATA(result); + +- /* Sort canonically. */ ++ /* Sort each consecutive combining-character run canonically. */ + i = 0; +- prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; +- for (i++; i < PyUnicode_GET_LENGTH(result); i++) { +- cur = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; +- if (prev == 0 || cur == 0 || prev <= cur) { +- prev = cur; ++ while (i < o) { ++ Py_ssize_t run_length, run_start; ++ int needs_sort = 0; ++ ++ Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i); ++ prev = _getrecord_ex(ch)->combining; ++ if (prev == 0) { ++ i++; + continue; + } +- /* Non-canonical order. Need to switch *i with previous. */ +- o = i - 1; +- while (1) { +- Py_UCS4 tmp = PyUnicode_READ(kind, data, o+1); +- PyUnicode_WRITE(kind, data, o+1, +- PyUnicode_READ(kind, data, o)); +- PyUnicode_WRITE(kind, data, o, tmp); +- o--; +- if (o < 0) +- break; +- prev = _getrecord_ex(PyUnicode_READ(kind, data, o))->combining; +- if (prev == 0 || prev <= cur) ++ ++ run_start = i++; ++ while (i < o) { ++ Py_UCS4 ch = PyUnicode_READ(result_kind, result_data, i); ++ cur = _getrecord_ex(ch)->combining; ++ if (cur == 0) { + break; ++ } ++ if (prev > cur) { ++ needs_sort = 1; ++ } ++ prev = cur; ++ i++; ++ } ++ if (!needs_sort) { ++ continue; ++ } ++ ++ run_length = i - run_start; ++ if (run_length < CANONICAL_ORDERING_COUNTING_SORT_THRESHOLD) { ++ canonical_ordering_sort_insertion(result_kind, result_data, ++ run_start, i); ++ continue; + } +- prev = _getrecord_ex(PyUnicode_READ(kind, data, i))->combining; ++ ++ if (run_length > sortbuflen) { ++ Py_UCS4 *new_sortbuf = PyMem_Resize(sortbuf, ++ Py_UCS4, ++ run_length); ++ if (new_sortbuf == NULL) { ++ PyErr_NoMemory(); ++ PyMem_Free(sortbuf); ++ Py_DECREF(result); ++ return NULL; ++ } ++ sortbuf = new_sortbuf; ++ sortbuflen = run_length; ++ } ++ ++ canonical_ordering_sort_counting(result_kind, result_data, ++ run_start, i, sortbuf); + } ++ PyMem_Free(sortbuf); + return result; + } + +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-3644.patch b/SPECS/python3/CVE-2026-3644.patch new file mode 100644 index 00000000000..560f297baf7 --- /dev/null +++ b/SPECS/python3/CVE-2026-3644.patch @@ -0,0 +1,158 @@ +From 09b91ed9fd8b149d117f8d166691cd4b6ccad68a Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:16:44 +0200 +Subject: [PATCH] gh-145599, CVE 2026-3644: Reject control characters in + `http.cookies.Morsel.update()` (GH-145600) (#146025) + +gh-145599, CVE 2026-3644: Reject control characters in `http.cookies.Morsel.update()` (GH-145600) + +Reject control characters in `http.cookies.Morsel.update()` and `http.cookies.BaseCookie.js_output`. +(cherry picked from commit 57e88c1cf95e1481b94ae57abe1010469d47a6b4) + +Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> +Co-authored-by: Victor Stinner +Co-authored-by: Victor Stinner +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/3974092b037f9a3b000fb15b48ea61ce3b25d330.patch +--- + Lib/http/cookies.py | 24 ++++++++++-- + Lib/test/test_http_cookies.py | 38 +++++++++++++++++++ + ...-03-06-17-03-38.gh-issue-145599.kchwZV.rst | 4 ++ + 3 files changed, 62 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst + +diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py +index d0a69cb..63d119a 100644 +--- a/Lib/http/cookies.py ++++ b/Lib/http/cookies.py +@@ -335,9 +335,16 @@ class Morsel(dict): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) ++ if _has_control_character(key, val): ++ raise CookieError("Control characters are not allowed in " ++ f"cookies {key!r} {val!r}") + data[key] = val + dict.update(self, data) + ++ def __ior__(self, values): ++ self.update(values) ++ return self ++ + def isReservedKey(self, K): + return K.lower() in self._reserved + +@@ -363,9 +370,15 @@ class Morsel(dict): + } + + def __setstate__(self, state): +- self._key = state['key'] +- self._value = state['value'] +- self._coded_value = state['coded_value'] ++ key = state['key'] ++ value = state['value'] ++ coded_value = state['coded_value'] ++ if _has_control_character(key, value, coded_value): ++ raise CookieError("Control characters are not allowed in cookies " ++ f"{key!r} {value!r} {coded_value!r}") ++ self._key = key ++ self._value = value ++ self._coded_value = coded_value + + def output(self, attrs=None, header="Set-Cookie:"): + return "%s %s" % (header, self.OutputString(attrs)) +@@ -377,13 +390,16 @@ class Morsel(dict): + + def js_output(self, attrs=None): + # Print javascript ++ output_string = self.OutputString(attrs) ++ if _has_control_character(output_string): ++ raise CookieError("Control characters are not allowed in cookies") + return """ + +- """ % (self.OutputString(attrs).replace('"', r'\"')) ++ """ % (output_string.replace('"', r'\"')) + + def OutputString(self, attrs=None): + # Build up our result +diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py +index f196bcc..2478a6c 100644 +--- a/Lib/test/test_http_cookies.py ++++ b/Lib/test/test_http_cookies.py +@@ -573,6 +573,14 @@ class MorselTests(unittest.TestCase): + with self.assertRaises(cookies.CookieError): + morsel["path"] = c0 + ++ # .__setstate__() ++ with self.assertRaises(cookies.CookieError): ++ morsel.__setstate__({'key': c0, 'value': 'val', 'coded_value': 'coded'}) ++ with self.assertRaises(cookies.CookieError): ++ morsel.__setstate__({'key': 'key', 'value': c0, 'coded_value': 'coded'}) ++ with self.assertRaises(cookies.CookieError): ++ morsel.__setstate__({'key': 'key', 'value': 'val', 'coded_value': c0}) ++ + # .setdefault() + with self.assertRaises(cookies.CookieError): + morsel.setdefault("path", c0) +@@ -587,6 +595,18 @@ class MorselTests(unittest.TestCase): + with self.assertRaises(cookies.CookieError): + morsel.set("path", "val", c0) + ++ # .update() ++ with self.assertRaises(cookies.CookieError): ++ morsel.update({"path": c0}) ++ with self.assertRaises(cookies.CookieError): ++ morsel.update({c0: "val"}) ++ ++ # .__ior__() ++ with self.assertRaises(cookies.CookieError): ++ morsel |= {"path": c0} ++ with self.assertRaises(cookies.CookieError): ++ morsel |= {c0: "val"} ++ + def test_control_characters_output(self): + # Tests that even if the internals of Morsel are modified + # that a call to .output() has control character safeguards. +@@ -607,6 +627,24 @@ class MorselTests(unittest.TestCase): + with self.assertRaises(cookies.CookieError): + cookie.output() + ++ # Tests that .js_output() also has control character safeguards. ++ for c0 in support.control_characters_c0(): ++ morsel = cookies.Morsel() ++ morsel.set("key", "value", "coded-value") ++ morsel._key = c0 # Override private variable. ++ cookie = cookies.SimpleCookie() ++ cookie["cookie"] = morsel ++ with self.assertRaises(cookies.CookieError): ++ cookie.js_output() ++ ++ morsel = cookies.Morsel() ++ morsel.set("key", "value", "coded-value") ++ morsel._coded_value = c0 # Override private variable. ++ cookie = cookies.SimpleCookie() ++ cookie["cookie"] = morsel ++ with self.assertRaises(cookies.CookieError): ++ cookie.js_output() ++ + + def load_tests(loader, tests, pattern): + tests.addTest(doctest.DocTestSuite(cookies)) +diff --git a/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst b/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst +new file mode 100644 +index 0000000..e53a932 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst +@@ -0,0 +1,4 @@ ++Reject control characters in :class:`http.cookies.Morsel` ++:meth:`~http.cookies.Morsel.update` and ++:meth:`~http.cookies.BaseCookie.js_output`. ++This addresses :cve:`2026-3644`. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-4360.patch b/SPECS/python3/CVE-2026-4360.patch new file mode 100644 index 00000000000..c09eeeb5768 --- /dev/null +++ b/SPECS/python3/CVE-2026-4360.patch @@ -0,0 +1,150 @@ +From 2d2695e3be4826a9189fdc8855f4688d391053f3 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:28:03 +0200 +Subject: [PATCH] gh-151987: Pass filter_function to TarFile._extract_one() + during .extract() (GH-151988) (#152611) + +gh-151987: Pass filter_function to TarFile._extract_one() during .extract() (GH-151988) + +(cherry picked from commit 7ccdbaba2c54250a70d7f25632152df7655a5e0a) + +Co-authored-by: Petr Viktorin +Co-authored-by: Seth Michael Larson +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/0367912be336348b30572f8029cec4a282782d92.patch +--- + Lib/tarfile.py | 3 +- + Lib/test/test_tarfile.py | 92 +++++++++++++++++++ + ...-06-23-14-19-30.gh-issue-151987.8mNIMf.rst | 2 + + 3 files changed, 96 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst + +diff --git a/Lib/tarfile.py b/Lib/tarfile.py +index 28c3edf..184f0ad 100755 +--- a/Lib/tarfile.py ++++ b/Lib/tarfile.py +@@ -2410,7 +2410,8 @@ class TarFile(object): + tarinfo, unfiltered = self._get_extract_tarinfo( + member, filter_function, path) + if tarinfo is not None: +- self._extract_one(tarinfo, path, set_attrs, numeric_owner) ++ self._extract_one(tarinfo, path, set_attrs, numeric_owner, ++ filter_function=filter_function) + + def _get_extract_tarinfo(self, member, filter_function, path): + """Get (filtered, unfiltered) TarInfos from *member* +diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py +index 8388dc4..e5ba18b 100644 +--- a/Lib/test/test_tarfile.py ++++ b/Lib/test/test_tarfile.py +@@ -4234,6 +4234,98 @@ class TestExtractionFilters(unittest.TestCase): + st_mode = cc.outerdir.stat().st_mode + self.assertNotEqual(st_mode & 0o777, 0o777) + ++ @symlink_test ++ @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown") ++ @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown") ++ @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid") ++ @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE)) ++ def test_chown_links_on_extract(self, link_type): ++ with ArchiveMaker() as arc: ++ arc.add("test.txt", ++ uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x') ++ arc.add("link", ++ type=link_type, ++ linkname='test.txt', ++ uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x') ++ ++ with ( ++ os_helper.temp_dir() as tmpdir, ++ arc.open() as tar, ++ unittest.mock.patch("os.chown") as mock_chown, ++ unittest.mock.patch("os.lchown") as mock_lchown, ++ unittest.mock.patch("os.geteuid") as mock_geteuid, ++ ): ++ # Set UID to 0 so chown() is attempted. ++ mock_geteuid.return_value = 0 ++ tar.extract("link", path=tmpdir, filter='data') ++ extract_path = os.path.join(tmpdir, "link") ++ ++ if link_type == tarfile.SYMTYPE: ++ mock_chown.assert_not_called() ++ mock_lchown.assert_called_once_with(extract_path, -1, -1) ++ else: ++ mock_chown.assert_has_calls([ ++ unittest.mock.call(extract_path, -1, -1), ++ unittest.mock.call(extract_path, -1, -1) ++ ]) ++ mock_lchown.assert_not_called() ++ ++ @symlink_test ++ @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown") ++ @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown") ++ @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid") ++ @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE)) ++ def test_chown_links_on_extractall(self, link_type): ++ with ArchiveMaker() as arc: ++ arc.add("test.txt", ++ uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x') ++ arc.add("link", ++ type=link_type, ++ linkname='test.txt', ++ uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x') ++ ++ with ( ++ os_helper.temp_dir() as tmpdir, ++ arc.open() as tar, ++ unittest.mock.patch("os.chown") as mock_chown, ++ unittest.mock.patch("os.lchown") as mock_lchown, ++ unittest.mock.patch("os.geteuid") as mock_geteuid, ++ ): ++ # Set UID to 0 so chown() is attempted. ++ mock_geteuid.return_value = 0 ++ tar.extractall(path=tmpdir, filter='data') ++ extract_link_path = os.path.join(tmpdir, "link") ++ extract_file_path = os.path.join(tmpdir, "test.txt") ++ ++ if link_type == tarfile.SYMTYPE: ++ mock_chown.assert_called_once_with(extract_file_path, -1, -1) ++ mock_lchown.assert_called_once_with(extract_link_path, -1, -1) ++ else: ++ mock_chown.assert_has_calls([ ++ unittest.mock.call(extract_file_path, -1, -1), ++ unittest.mock.call(extract_link_path, -1, -1) ++ ]) ++ mock_lchown.assert_not_called() ++ ++ def test_extract_filters_target(self): ++ # Test that when extract() falls back to extracting (rather than ++ # linking) a hardlink target, it filters the target. ++ with ArchiveMaker() as arc: ++ arc.add("target") ++ arc.add("link", hardlink_to="target") ++ def testing_filter(member, path): ++ if member.name == 'target': ++ # target: set read-only ++ return member.replace(mode=stat.S_IRUSR) ++ # link: don't overwrite the mode ++ return member.replace(mode=None) ++ tempdir = pathlib.Path(TEMPDIR) / 'extract' ++ with os_helper.temp_dir(tempdir), arc.open() as tar: ++ tar.extract("link", path=tempdir, filter=testing_filter) ++ path = tempdir / 'link' ++ if os_helper.can_chmod(): ++ self.assertFalse(path.stat().st_mode & stat.S_IWUSR) ++ + def test_link_fallback_normalizes(self): + # Make sure hardlink fallbacks work for non-normalized paths for all + # filters +diff --git a/Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst b/Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst +new file mode 100644 +index 0000000..9eea7b3 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst +@@ -0,0 +1,2 @@ ++The :meth:`tarfile.TarFile.extract` method now applies the given filter when ++it extracts a link target from the archive as a fallback. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-4786.patch b/SPECS/python3/CVE-2026-4786.patch new file mode 100644 index 00000000000..5b8511e238f --- /dev/null +++ b/SPECS/python3/CVE-2026-4786.patch @@ -0,0 +1,70 @@ +From cad77e71f399532cb100fa9b54b8c01d53d2e319 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Tue, 4 Aug 2026 11:19:00 +0200 +Subject: [PATCH] gh-148169: Fix webbrowser `%action` substitution bypass of + dash-prefix check (GH-148170) (#148519) + +(cherry picked from commit d22922c8a7958353689dc4763dd72da2dea03fff) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/a4d3edf3a6ecfde504d02126410d2a65a859b744.patch +--- + 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 60f094f..e900c02 100644 +--- a/Lib/test/test_webbrowser.py ++++ b/Lib/test/test_webbrowser.py +@@ -99,6 +99,15 @@ class ChromeCommandTest(CommandTestMixin, unittest.TestCase): + options=[], + arguments=[URL]) + ++ 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 0bdb644..79d410b 100755 +--- a/Lib/webbrowser.py ++++ b/Lib/webbrowser.py +@@ -268,7 +268,6 @@ class UnixBrowser(BaseBrowser): + + 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: +@@ -282,7 +281,9 @@ class UnixBrowser(BaseBrowser): + raise Error("Bad 'new' parameter to open(); " + + "expected 0, 1, or 2, got %s" % 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 0000000..45cdeeb +--- /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. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-6100.patch b/SPECS/python3/CVE-2026-6100.patch new file mode 100644 index 00000000000..68f5bd0eff5 --- /dev/null +++ b/SPECS/python3/CVE-2026-6100.patch @@ -0,0 +1,73 @@ +From eb25fd4ced8473344a8d776710809432a284b981 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:18:25 +0200 +Subject: [PATCH] gh-148395: Fix a possible UAF in + `{LZMA,BZ2,_Zlib}Decompressor` (GH-148396) (#148503) + +* gh-148395: Fix a possible UAF in `{LZMA,BZ2,_Zlib}Decompressor` (GH-148396) + +Fix dangling input pointer after `MemoryError` in _lzma/_bz2/_ZlibDecompressor.decompress +(cherry picked from commit 8fc66aef6d7b3ae58f43f5c66f9366cc8cbbfcd2) + +Co-authored-by: Stan Ulbrych +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/ea8d735eb084cf8cc021df1a30e90d10a8f052e3.patch +--- + .../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 0000000..9502189 +--- /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 97bd44b..a732e89 100644 +--- a/Modules/_bz2module.c ++++ b/Modules/_bz2module.c +@@ -587,6 +587,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 7bbd656..103a6ef 100644 +--- a/Modules/_lzmamodule.c ++++ b/Modules/_lzmamodule.c +@@ -1114,6 +1114,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 f94c57e..9759593 100644 +--- a/Modules/zlibmodule.c ++++ b/Modules/zlibmodule.c +@@ -1645,6 +1645,7 @@ decompress(ZlibDecompressor *self, uint8_t *data, + return result; + + error: ++ self->zst.next_in = NULL; + Py_XDECREF(result); + return NULL; + } +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-6879.patch b/SPECS/python3/CVE-2026-6879.patch new file mode 100644 index 00000000000..46ea5bc6303 --- /dev/null +++ b/SPECS/python3/CVE-2026-6879.patch @@ -0,0 +1,115 @@ +From 6be3ce8aba772fbe74a4efccf018d96dd49077e3 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 4 Aug 2026 11:30:44 +0200 +Subject: [PATCH] gh-152674: Avoid quadratic behavior in xml.etree.ElementPath + index predicates (GH-152676) (#154815) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +gh-152674: Avoid quadratic behavior in xml.etree.ElementPath index predicates (GH-152676) + +(cherry picked from commit 2ffab083782968a4d732738f4f1dff6bbd69d2b0) + +Co-authored-by: Petr Viktorin +Co-authored-by: Alexis +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/96510a3758f4a075f43223afdee3b6ee1a7a7f02.patch +--- + Lib/test/test_xml_etree.py | 31 +++++++++++++++++++ + Lib/xml/etree/ElementPath.py | 17 +++++++--- + ...-06-30-13-24-13.gh-issue-152674.-2QVoL.rst | 6 ++++ + 3 files changed, 49 insertions(+), 5 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-06-30-13-24-13.gh-issue-152674.-2QVoL.rst + +diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py +index f8c2e5c..9933cab 100644 +--- a/Lib/test/test_xml_etree.py ++++ b/Lib/test/test_xml_etree.py +@@ -2896,6 +2896,37 @@ class ElementFindTest(unittest.TestCase): + self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]') + self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]') + ++ def test_find_xpath_index_no_quadratic_complexity(self): ++ class CountingElement(ET.Element): ++ findall_calls = 0 ++ def findall(self, *args, **kwargs): ++ type(self).findall_calls += 1 ++ return super().findall(*args, **kwargs) ++ ++ def work(n, pattern): ++ root = CountingElement("root") ++ for _ in range(n): ++ ET.SubElement(root, "a") ++ CountingElement.findall_calls = 0 ++ root.findall(pattern) ++ return CountingElement.findall_calls ++ ++ for pattern in [".//a[1]", ".//a[last()]"]: ++ w1 = work(1024, pattern) ++ w2 = work(2048, pattern) ++ w3 = work(4096, pattern) ++ ++ self.assertGreater(w1, 0) ++ r1 = w2 / w1 ++ r2 = w3 / w2 ++ # Doubling N must not ~double the parent.findall calls. ++ # Linear-in-N call counts indicate the cache is missing. ++ self.assertLess( ++ max(r1, r2), 1.5, ++ msg=f"Possible quadratic behavior on {pattern!r}: " ++ f"calls={w1, w2, w3} ratios={r1, r2}", ++ ) ++ + def test_findall(self): + e = ET.XML(SAMPLE_XML) + e[2] = ET.XML(SAMPLE_SECTION) +diff --git a/Lib/xml/etree/ElementPath.py b/Lib/xml/etree/ElementPath.py +index dc6bd28..de1fd20 100644 +--- a/Lib/xml/etree/ElementPath.py ++++ b/Lib/xml/etree/ElementPath.py +@@ -324,15 +324,22 @@ def prepare_predicate(next, token): + index = -1 + def select(context, result): + parent_map = get_parent_map(context) ++ cache = {} + for elem in result: + try: + parent = parent_map[elem] ++ except KeyError: ++ continue ++ key = (parent, elem.tag) ++ if key not in cache: + # FIXME: what if the selector is "*" ? +- elems = list(parent.findall(elem.tag)) +- if elems[index] is elem: +- yield elem +- except (IndexError, KeyError): +- pass ++ elems = parent.findall(elem.tag) ++ try: ++ cache[key] = elems[index] ++ except IndexError: ++ cache[key] = None ++ if cache[key] is elem: ++ yield elem + return select + raise SyntaxError("invalid predicate") + +diff --git a/Misc/NEWS.d/next/Security/2026-06-30-13-24-13.gh-issue-152674.-2QVoL.rst b/Misc/NEWS.d/next/Security/2026-06-30-13-24-13.gh-issue-152674.-2QVoL.rst +new file mode 100644 +index 0000000..69e7300 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-06-30-13-24-13.gh-issue-152674.-2QVoL.rst +@@ -0,0 +1,6 @@ ++The :class:`xml.etree.ElementTree.Element` methods ++:meth:`~xml.etree.ElementTree.Element.findall`, ++:meth:`~xml.etree.ElementTree.Element.iterfind` and ++:meth:`~xml.etree.ElementTree.Element.find` avoid quadratic behavior when ++using XPath index predicates (``[1]``, ``[last()]``, ``[last()-N]``) on XML ++documents with many same-tag siblings. +-- +2.45.4 + diff --git a/SPECS/python3/CVE-2026-9669.patch b/SPECS/python3/CVE-2026-9669.patch new file mode 100644 index 00000000000..da11bcf5736 --- /dev/null +++ b/SPECS/python3/CVE-2026-9669.patch @@ -0,0 +1,109 @@ +From adcdd91d9caa0e062322f9a914da96e65d47d8fc Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Tue, 4 Aug 2026 11:22:54 +0200 +Subject: [PATCH] gh-150599: Prevent bz2 decompressor reuse after errors + (#150600) (#151057) + +* [3.12] gh-150599: Prevent bz2 decompressor reuse after errors (#150600) (#151054) + +(cherry picked from commit 5755d0f083949ff3c5bf3a37e673e24e306b036e) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/python/cpython/commit/991e6cf86496718c4ef00b362d640e00cb5c85b2.patch +--- + Lib/test/test_bz2.py | 15 +++++++++++++++ + ...6-05-30-09-36-20.gh-issue-150599.nlHqU-.rst | 3 +++ + Modules/_bz2module.c | 18 +++++++++++++++--- + 3 files changed, 33 insertions(+), 3 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst + +diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py +index cb730a1..dcbf6a2 100644 +--- a/Lib/test/test_bz2.py ++++ b/Lib/test/test_bz2.py +@@ -958,6 +958,21 @@ class BZ2DecompressorTest(BaseTest): + # Previously, a second call could crash due to internal inconsistency + self.assertRaises(Exception, bzd.decompress, self.BAD_DATA * 30) + ++ def test_decompress_after_data_error(self): ++ data = bytes.fromhex( ++ "425a6839314159265359000000000000007fffff000000000000000000000000" ++ "00000000000000000000000000000000000000e0370000000000000000000000" ++ "000000000000000000000000000000000000000000000000000083f3" ++ ) ++ bzd = BZ2Decompressor() ++ with self.assertRaisesRegex(OSError, "Invalid data stream"): ++ bzd.decompress(data) ++ # Previously, a second call could crash due to internal inconsistency ++ self.assertFalse(bzd.needs_input) ++ self.assertFalse(bzd.eof) ++ with self.assertRaisesRegex(ValueError, "previous error"): ++ bzd.decompress(b'\x00' * 18) ++ + @support.refcount_test + def test_refleaks_in___init__(self): + gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount') +diff --git a/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst b/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst +new file mode 100644 +index 0000000..a37d86c +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-05-30-09-36-20.gh-issue-150599.nlHqU-.rst +@@ -0,0 +1,3 @@ ++Fix a possible stack buffer overflow in :mod:`bz2` when a ++:class:`bz2.BZ2Decompressor` is reused after a decompression error. ++The decompressor now becomes unusable after libbz2 reports an error. +diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c +index a732e89..4faa1b3 100644 +--- a/Modules/_bz2module.c ++++ b/Modules/_bz2module.c +@@ -114,6 +114,7 @@ typedef struct { + typedef struct { + PyObject_HEAD + bz_stream bzs; ++ int bzerror; + char eof; /* T_BOOL expects a char */ + PyObject *unused_data; + char needs_input; +@@ -453,8 +454,11 @@ decompress_buf(BZ2Decompressor *d, Py_ssize_t max_length) + + d->bzs_avail_in_real += bzs->avail_in; + +- if (catch_bz2_error(bzret)) ++ if (catch_bz2_error(bzret)) { ++ d->bzerror = bzret; ++ d->needs_input = 0; + goto error; ++ } + if (bzret == BZ_STREAM_END) { + d->eof = 1; + break; +@@ -622,10 +626,17 @@ _bz2_BZ2Decompressor_decompress_impl(BZ2Decompressor *self, Py_buffer *data, + PyObject *result = NULL; + + ACQUIRE_LOCK(self); +- if (self->eof) ++ if (self->eof) { + PyErr_SetString(PyExc_EOFError, "End of stream already reached"); +- else ++ } ++ else if (self->bzerror) { ++ // Re-entering BZ2_bzDecompress() after an error can write out of bounds. ++ PyErr_SetString(PyExc_ValueError, ++ "Decompressor is unusable after a previous error"); ++ } ++ else { + result = decompress(self, data->buf, data->len, max_length); ++ } + RELEASE_LOCK(self); + return result; + } +@@ -659,6 +670,7 @@ _bz2_BZ2Decompressor_impl(PyTypeObject *type) + return NULL; + } + ++ self->bzerror = 0; + self->needs_input = 1; + self->bzs_avail_in_real = 0; + self->input_buffer = NULL; +-- +2.45.4 + diff --git a/SPECS/python3/python3.spec b/SPECS/python3/python3.spec index a6735e70ca8..ad5e4630d99 100644 --- a/SPECS/python3/python3.spec +++ b/SPECS/python3/python3.spec @@ -6,7 +6,7 @@ Summary: A high-level scripting language Name: python3 Version: 3.12.9 -Release: 13%{?dist} +Release: 14%{?dist} License: PSF Vendor: Microsoft Corporation Distribution: Azure Linux @@ -35,6 +35,18 @@ Patch15: CVE-2026-1502.patch Patch16: CVE-2025-13462.patch Patch17: CVE-2026-8328.patch Patch18: CVE-2026-7774.patch +Patch19: CVE-2026-0864.patch +Patch20: CVE-2026-11972.patch +Patch21: CVE-2026-12003.patch +Patch22: CVE-2026-2297.patch +Patch23: CVE-2026-3087.patch +Patch24: CVE-2026-3276.patch +Patch25: CVE-2026-3644.patch +Patch26: CVE-2026-4360.patch +Patch27: CVE-2026-4786.patch +Patch28: CVE-2026-6100.patch +Patch29: CVE-2026-6879.patch +Patch30: CVE-2026-9669.patch BuildRequires: bzip2-devel BuildRequires: expat-devel >= 2.1.0 @@ -257,6 +269,9 @@ rm -rf %{buildroot}%{_bindir}/__pycache__ %{_libdir}/python%{majmin}/test/* %changelog +* Wed Aug 05 2026 Azure Linux Security Servicing Account - 3.12.9-14 +- Patch for CVE-2026-9669, CVE-2026-6879, CVE-2026-6100, CVE-2026-4786, CVE-2026-4360, CVE-2026-3644, CVE-2026-3276, CVE-2026-3087, CVE-2026-2297, CVE-2026-12003, CVE-2026-11972, CVE-2026-0864 + * Wed Jun 10 2026 Azure Linux Security Servicing Account - 3.12.9-13 - Patch for CVE-2026-7774 diff --git a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt index aaca282c263..84b177681b8 100644 --- a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt +++ b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt @@ -244,9 +244,9 @@ ca-certificates-base-3.0.0-14.azl3.noarch.rpm ca-certificates-3.0.0-14.azl3.noarch.rpm dwz-0.14-2.azl3.aarch64.rpm unzip-6.0-22.azl3.aarch64.rpm -python3-3.12.9-13.azl3.aarch64.rpm -python3-devel-3.12.9-13.azl3.aarch64.rpm -python3-libs-3.12.9-13.azl3.aarch64.rpm +python3-3.12.9-14.azl3.aarch64.rpm +python3-devel-3.12.9-14.azl3.aarch64.rpm +python3-libs-3.12.9-14.azl3.aarch64.rpm python3-setuptools-69.0.3-5.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm which-2.21-8.azl3.aarch64.rpm diff --git a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt index 3d759e2e9eb..0b06e840e36 100644 --- a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt +++ b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt @@ -244,9 +244,9 @@ ca-certificates-base-3.0.0-14.azl3.noarch.rpm ca-certificates-3.0.0-14.azl3.noarch.rpm dwz-0.14-2.azl3.x86_64.rpm unzip-6.0-22.azl3.x86_64.rpm -python3-3.12.9-13.azl3.x86_64.rpm -python3-devel-3.12.9-13.azl3.x86_64.rpm -python3-libs-3.12.9-13.azl3.x86_64.rpm +python3-3.12.9-14.azl3.x86_64.rpm +python3-devel-3.12.9-14.azl3.x86_64.rpm +python3-libs-3.12.9-14.azl3.x86_64.rpm python3-setuptools-69.0.3-5.azl3.noarch.rpm python3-pygments-2.7.4-2.azl3.noarch.rpm which-2.21-8.azl3.x86_64.rpm diff --git a/toolkit/resources/manifests/package/toolchain_aarch64.txt b/toolkit/resources/manifests/package/toolchain_aarch64.txt index 30a8acae6de..2965d643479 100644 --- a/toolkit/resources/manifests/package/toolchain_aarch64.txt +++ b/toolkit/resources/manifests/package/toolchain_aarch64.txt @@ -531,19 +531,19 @@ pyproject-rpm-macros-1.12.0-2.azl3.noarch.rpm pyproject-srpm-macros-1.12.0-2.azl3.noarch.rpm python-markupsafe-debuginfo-2.1.3-1.azl3.aarch64.rpm python-wheel-wheel-0.43.0-2.azl3.noarch.rpm -python3-3.12.9-13.azl3.aarch64.rpm +python3-3.12.9-14.azl3.aarch64.rpm python3-audit-3.1.2-1.azl3.aarch64.rpm python3-cracklib-2.9.11-1.azl3.aarch64.rpm -python3-curses-3.12.9-13.azl3.aarch64.rpm +python3-curses-3.12.9-14.azl3.aarch64.rpm python3-Cython-3.0.5-3.azl3.aarch64.rpm -python3-debuginfo-3.12.9-13.azl3.aarch64.rpm -python3-devel-3.12.9-13.azl3.aarch64.rpm +python3-debuginfo-3.12.9-14.azl3.aarch64.rpm +python3-devel-3.12.9-14.azl3.aarch64.rpm python3-flit-core-3.9.0-1.azl3.noarch.rpm python3-gpg-1.23.2-2.azl3.aarch64.rpm python3-jinja2-3.1.2-3.azl3.noarch.rpm python3-libcap-ng-0.8.4-1.azl3.aarch64.rpm python3-libmount-2.40.2-5.azl3.aarch64.rpm -python3-libs-3.12.9-13.azl3.aarch64.rpm +python3-libs-3.12.9-14.azl3.aarch64.rpm python3-libxml2-2.11.5-10.azl3.aarch64.rpm python3-lxml-4.9.3-2.azl3.aarch64.rpm python3-magic-5.45-1.azl3.noarch.rpm @@ -555,8 +555,8 @@ python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.aarch64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm python3-setuptools-69.0.3-5.azl3.noarch.rpm -python3-test-3.12.9-13.azl3.aarch64.rpm -python3-tools-3.12.9-13.azl3.aarch64.rpm +python3-test-3.12.9-14.azl3.aarch64.rpm +python3-tools-3.12.9-14.azl3.aarch64.rpm python3-wheel-0.43.0-2.azl3.noarch.rpm readline-8.2-2.azl3.aarch64.rpm readline-debuginfo-8.2-2.azl3.aarch64.rpm diff --git a/toolkit/resources/manifests/package/toolchain_x86_64.txt b/toolkit/resources/manifests/package/toolchain_x86_64.txt index e31e00b8bc4..8fd46a1652d 100644 --- a/toolkit/resources/manifests/package/toolchain_x86_64.txt +++ b/toolkit/resources/manifests/package/toolchain_x86_64.txt @@ -539,19 +539,19 @@ pyproject-rpm-macros-1.12.0-2.azl3.noarch.rpm pyproject-srpm-macros-1.12.0-2.azl3.noarch.rpm python-markupsafe-debuginfo-2.1.3-1.azl3.x86_64.rpm python-wheel-wheel-0.43.0-2.azl3.noarch.rpm -python3-3.12.9-13.azl3.x86_64.rpm +python3-3.12.9-14.azl3.x86_64.rpm python3-audit-3.1.2-1.azl3.x86_64.rpm python3-cracklib-2.9.11-1.azl3.x86_64.rpm -python3-curses-3.12.9-13.azl3.x86_64.rpm +python3-curses-3.12.9-14.azl3.x86_64.rpm python3-Cython-3.0.5-3.azl3.x86_64.rpm -python3-debuginfo-3.12.9-13.azl3.x86_64.rpm -python3-devel-3.12.9-13.azl3.x86_64.rpm +python3-debuginfo-3.12.9-14.azl3.x86_64.rpm +python3-devel-3.12.9-14.azl3.x86_64.rpm python3-flit-core-3.9.0-1.azl3.noarch.rpm python3-gpg-1.23.2-2.azl3.x86_64.rpm python3-jinja2-3.1.2-3.azl3.noarch.rpm python3-libcap-ng-0.8.4-1.azl3.x86_64.rpm python3-libmount-2.40.2-5.azl3.x86_64.rpm -python3-libs-3.12.9-13.azl3.x86_64.rpm +python3-libs-3.12.9-14.azl3.x86_64.rpm python3-libxml2-2.11.5-10.azl3.x86_64.rpm python3-lxml-4.9.3-2.azl3.x86_64.rpm python3-magic-5.45-1.azl3.noarch.rpm @@ -563,8 +563,8 @@ python3-pygments-2.7.4-2.azl3.noarch.rpm python3-rpm-4.18.2-1.azl3.x86_64.rpm python3-rpm-generators-14-11.azl3.noarch.rpm python3-setuptools-69.0.3-5.azl3.noarch.rpm -python3-test-3.12.9-13.azl3.x86_64.rpm -python3-tools-3.12.9-13.azl3.x86_64.rpm +python3-test-3.12.9-14.azl3.x86_64.rpm +python3-tools-3.12.9-14.azl3.x86_64.rpm python3-wheel-0.43.0-2.azl3.noarch.rpm readline-8.2-2.azl3.x86_64.rpm readline-debuginfo-8.2-2.azl3.x86_64.rpm