https://github.com/python/cpython/commit/c95239211124d1b75f351f259133e35a9c1e0bd6
commit: c95239211124d1b75f351f259133e35a9c1e0bd6
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T05:19:57Z
summary:

[3.15] gh-160297: Fix tarfile setting attributes through a hard link fallback 
(GH-160298) (#161058)

gh-160297: Fix tarfile setting attributes through a hard link fallback 
(GH-160298)

When a hard link member is extracted as a copy of a symlink member, do
not apply the hard link's owner, mode and mtime to the symlink's target.

ANT-2026-Y0H1Y0ER
(cherry picked from commit e5f14773700817f8cbbcd935192f6202d0821aed)

Co-authored-by: Gregory P. Smith <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-22-08-04-26.gh-issue-160297.hL4kQz.rst
M Lib/tarfile.py
M Lib/test/test_tarfile.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 38a389bcab158f..02bdd1d3f078cf 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2722,6 +2722,13 @@ def _extract_member(self, tarinfo, targetpath, 
set_attrs=True,
         else:
             self.makefile(tarinfo, targetpath)
 
+        if tarinfo.islnk() and os.path.islink(targetpath):
+            # makelink_with_filter() fell back to extracting a copy of a
+            # symlink member, which already has its own attributes set.
+            # Setting the hard link's attributes here would follow the
+            # symlink, possibly to a file outside the destination directory.
+            set_attrs = False
+
         if set_attrs:
             self.chown(tarinfo, targetpath, numeric_owner)
             if not tarinfo.issym():
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 5ee389e705df61..0325ee1b00ed11 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -4656,6 +4656,33 @@ def test_sneaky_hardlink_fallback_deep(self):
                     self.expect_file("a/b/s", symlink_to=os.path.join('..', 
'escape'))
                     self.expect_file("s", symlink_to=os.path.join('..', 
'escape'))
 
+    @symlink_test
+    def test_sneaky_hardlink_fallback_attrs(self):
+        # Test that when a hardlink is extracted as a copy of a symlink
+        # member, the hardlink's attributes are not applied to the file
+        # the symlink points to.
+        with ArchiveMaker() as arc:
+            arc.add("a/s", symlink_to=os.path.join("..", "flag"))
+            arc.add("s", hardlink_to=os.path.join("a", "s"),
+                    mode='-rwxr-xr-x', mtime=86400, uid=1337, gid=1337)
+
+        for filter in 'tar', 'fully_trusted':
+            with (
+                self.subTest(filter),
+                unittest.mock.patch("os.chown", create=True) as mock_chown,
+                self.check_context(arc.open(), filter) as cc,
+            ):
+                if not os_helper.can_symlink():
+                    self.expect_file("a/")
+                else:
+                    self.expect_file("a/s", symlink_to=os.path.join('..', 
'flag'))
+                    self.expect_file("s", symlink_to=os.path.join('..', 
'flag'))
+                st = (cc.outerdir / 'flag').stat()
+                self.assertNotEqual(st.st_mtime, 86400)
+                if sys.platform != "win32":
+                    self.assertNotEqual(st.st_mode & 0o777, 0o755)
+                mock_chown.assert_not_called()
+
     @symlink_test
     @os_helper.skip_unless_hardlink
     def test_sneaky_hardlink_relocation(self):
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-22-08-04-26.gh-issue-160297.hL4kQz.rst 
b/Misc/NEWS.d/next/Library/2026-10-22-08-04-26.gh-issue-160297.hL4kQz.rst
new file mode 100644
index 00000000000000..1419910948fe15
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-08-04-26.gh-issue-160297.hL4kQz.rst
@@ -0,0 +1,4 @@
+Fix :meth:`tarfile.TarFile.extractall` and :meth:`tarfile.TarFile.extract`
+changing the mode, modification time and owner of a file outside the
+destination directory when a hard link member is extracted as a copy of a
+symbolic link member, for example with the ``'tar'`` filter.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to