diff mbox series

python3: fix CVE-2026-4360

Message ID 20260729101257.429392-1-leonid.iziumtsev@est.tech
State Under Review
Headers show
Series python3: fix CVE-2026-4360 | expand

Commit Message

Leonid Iziumtsev July 29, 2026, 10:12 a.m. UTC
Backport patch to fix CVE-2026-4360.

Reference:
  https://nvd.nist.gov/vuln/detail/CVE-2026-4360

Upstream fix:
  https://github.com/python/cpython/commit/5e0ef3f1afe892e4f64eb83368db57ac4c40cba0

Signed-off-by: Leonid Iziumtsev <leonid.iziumtsev@est.tech>
---
 .../python/python3/CVE-2026-4360.patch        | 148 ++++++++++++++++++
 .../recipes-devtools/python/python3_3.14.6.bb |   1 +
 2 files changed, 149 insertions(+)
 create mode 100644 meta/recipes-devtools/python/python3/CVE-2026-4360.patch
diff mbox series

Patch

diff --git a/meta/recipes-devtools/python/python3/CVE-2026-4360.patch b/meta/recipes-devtools/python/python3/CVE-2026-4360.patch
new file mode 100644
index 0000000000..d381508959
--- /dev/null
+++ b/meta/recipes-devtools/python/python3/CVE-2026-4360.patch
@@ -0,0 +1,148 @@ 
+From 66c8bc346c0c614edc05535145c0424a14fba213 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Mon, 29 Jun 2026 21:11:22 +0200
+Subject: [PATCH] gh-151987: Pass filter_function to `TarFile._extract_one()`
+ during `.extract()` (GH-151988) (#152609)
+
+(cherry picked from commit 7ccdbaba2c54250a70d7f25632152df7655a5e0a)
+
+Co-authored-by: Petr Viktorin <encukou@gmail.com>
+Co-authored-by: Seth Michael Larson <seth@python.org>
+
+CVE: CVE-2026-4360
+Upstream-Status: Backport [https://github.com/python/cpython/commit/5e0ef3f1afe892e4f64eb83368db57ac4c40cba0]
+
+Signed-off-by: Leonid Iziumtsev <leonid.iziumtsev@est.tech>
+---
+ 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 e6734db..2c46179 100644
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -2510,7 +2510,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 d974c7d..9a15585 100644
+--- a/Lib/test/test_tarfile.py
++++ b/Lib/test/test_tarfile.py
+@@ -4399,6 +4399,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.
diff --git a/meta/recipes-devtools/python/python3_3.14.6.bb b/meta/recipes-devtools/python/python3_3.14.6.bb
index a45a456133..77333ce3e2 100644
--- a/meta/recipes-devtools/python/python3_3.14.6.bb
+++ b/meta/recipes-devtools/python/python3_3.14.6.bb
@@ -24,6 +24,7 @@  SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \
            file://0001-prefer-valid-entrypoints.patch \
            file://CVE-2026-11940.patch \
            file://CVE-2026-11972.patch \
+           file://CVE-2026-4360.patch \
            "
 SRC_URI:append:class-native = " \
            file://0001-Lib-sysconfig.py-use-prefix-value-from-build-configu.patch \