Ticket #5972: python3-3.14-cve-2026-4360.patch

File python3-3.14-cve-2026-4360.patch, 6.6 KB (added by Joe Locash, 28 hours ago)
  • Lib/tarfile.py

    From aeb681725d059bbabf9ca190b805124de8d03c8f Mon Sep 17 00:00:00 2001
    From: Petr Viktorin <encukou@gmail.com>
    Date: Mon, 29 Jun 2026 17:41:26 +0200
    Subject: [PATCH] gh-151987: Pass filter_function to TarFile._extract_one()
     during .extract() (GH-151988)
    
    (cherry picked from commit 7ccdbaba2c54250a70d7f25632152df7655a5e0a)
    
    Co-authored-by: Petr Viktorin <encukou@gmail.com>
    Co-authored-by: Seth Michael Larson <seth@python.org>
    ---
     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 cb09e307c469bd..d3c489997006e7 100644
    a b def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,  
    25382538        tarinfo, unfiltered = self._get_extract_tarinfo(
    25392539            member, filter_function, path)
    25402540        if tarinfo is not None:
    2541             self._extract_one(tarinfo, path, set_attrs, numeric_owner)
     2541            self._extract_one(tarinfo, path, set_attrs, numeric_owner,
     2542                              filter_function=filter_function)
    25422543
    25432544    def _get_extract_tarinfo(self, member, filter_function, path):
    25442545        """Get (filtered, unfiltered) TarInfos from *member*
  • Lib/test/test_tarfile.py

    diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
    index 804c3e6d809487..f3b61d9fbad6a6 100644
    a b def test_chmod_outside_dir(self):  
    44704470                    st_mode = cc.outerdir.stat().st_mode
    44714471                    self.assertNotEqual(st_mode & 0o777, 0o777)
    44724472
     4473    @symlink_test
     4474    @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
     4475    @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
     4476    @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
     4477    @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
     4478    def test_chown_links_on_extract(self, link_type):
     4479        with ArchiveMaker() as arc:
     4480            arc.add("test.txt",
     4481                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
     4482            arc.add("link",
     4483                    type=link_type,
     4484                    linkname='test.txt',
     4485                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
     4486
     4487        with (
     4488            os_helper.temp_dir() as tmpdir,
     4489            arc.open() as tar,
     4490            unittest.mock.patch("os.chown") as mock_chown,
     4491            unittest.mock.patch("os.lchown") as mock_lchown,
     4492            unittest.mock.patch("os.geteuid") as mock_geteuid,
     4493        ):
     4494            # Set UID to 0 so chown() is attempted.
     4495            mock_geteuid.return_value = 0
     4496            tar.extract("link", path=tmpdir, filter='data')
     4497            extract_path = os.path.join(tmpdir, "link")
     4498
     4499            if link_type == tarfile.SYMTYPE:
     4500                mock_chown.assert_not_called()
     4501                mock_lchown.assert_called_once_with(extract_path, -1, -1)
     4502            else:
     4503                mock_chown.assert_has_calls([
     4504                    unittest.mock.call(extract_path, -1, -1),
     4505                    unittest.mock.call(extract_path, -1, -1)
     4506                ])
     4507                mock_lchown.assert_not_called()
     4508
     4509    @symlink_test
     4510    @unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
     4511    @unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
     4512    @unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
     4513    @support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
     4514    def test_chown_links_on_extractall(self, link_type):
     4515        with ArchiveMaker() as arc:
     4516            arc.add("test.txt",
     4517                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
     4518            arc.add("link",
     4519                    type=link_type,
     4520                    linkname='test.txt',
     4521                    uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
     4522
     4523        with (
     4524            os_helper.temp_dir() as tmpdir,
     4525            arc.open() as tar,
     4526            unittest.mock.patch("os.chown") as mock_chown,
     4527            unittest.mock.patch("os.lchown") as mock_lchown,
     4528            unittest.mock.patch("os.geteuid") as mock_geteuid,
     4529        ):
     4530            # Set UID to 0 so chown() is attempted.
     4531            mock_geteuid.return_value = 0
     4532            tar.extractall(path=tmpdir, filter='data')
     4533            extract_link_path = os.path.join(tmpdir, "link")
     4534            extract_file_path = os.path.join(tmpdir, "test.txt")
     4535
     4536            if link_type == tarfile.SYMTYPE:
     4537                mock_chown.assert_called_once_with(extract_file_path, -1, -1)
     4538                mock_lchown.assert_called_once_with(extract_link_path, -1, -1)
     4539            else:
     4540                mock_chown.assert_has_calls([
     4541                    unittest.mock.call(extract_file_path, -1, -1),
     4542                    unittest.mock.call(extract_link_path, -1, -1)
     4543                ])
     4544                mock_lchown.assert_not_called()
     4545
     4546    def test_extract_filters_target(self):
     4547        # Test that when extract() falls back to extracting (rather than
     4548        # linking) a hardlink target, it filters the target.
     4549        with ArchiveMaker() as arc:
     4550            arc.add("target")
     4551            arc.add("link", hardlink_to="target")
     4552        def testing_filter(member, path):
     4553            if member.name == 'target':
     4554                # target: set read-only
     4555                return member.replace(mode=stat.S_IRUSR)
     4556            # link: don't overwrite the mode
     4557            return member.replace(mode=None)
     4558        tempdir = pathlib.Path(TEMPDIR) / 'extract'
     4559        with os_helper.temp_dir(tempdir), arc.open() as tar:
     4560            tar.extract("link", path=tempdir, filter=testing_filter)
     4561            path = tempdir / 'link'
     4562            if os_helper.can_chmod():
     4563                self.assertFalse(path.stat().st_mode & stat.S_IWUSR)
     4564
    44734565    def test_link_fallback_normalizes(self):
    44744566        # Make sure hardlink fallbacks work for non-normalized paths for all
    44754567        # filters
  • new file Misc/NEWS.d/next/Security/2026-06-23-14-19-30.gh-issue-151987.8mNIMf.rst

    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 00000000000000..9eea7b32c4d2b4
    - +  
     1The :meth:`tarfile.TarFile.extract` method now applies the given filter when
     2it extracts a link target from the archive as a fallback.