https://github.com/python/cpython/commit/e5f14773700817f8cbbcd935192f6202d0821aed
commit: e5f14773700817f8cbbcd935192f6202d0821aed
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-10-31T21:48:37-07:00
summary:
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
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 700b0ac1a19da8..7a017223ea37f8 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2735,6 +2735,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 dce56fb13dfcb6..af7f405c631a59 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -4883,6 +4883,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]