Skip to content

Commit e5f1477

Browse files
authored
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
1 parent 3c23ebf commit e5f1477

3 files changed

Lines changed: 38 additions & 0 deletions

File tree

‎Lib/tarfile.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,6 +2735,13 @@ def _extract_member(self, tarinfo, targetpath, set_attrs=True,
27352735
else:
27362736
self.makefile(tarinfo, targetpath)
27372737

2738+
if tarinfo.islnk() and os.path.islink(targetpath):
2739+
# makelink_with_filter() fell back to extracting a copy of a
2740+
# symlink member, which already has its own attributes set.
2741+
# Setting the hard link's attributes here would follow the
2742+
# symlink, possibly to a file outside the destination directory.
2743+
set_attrs = False
2744+
27382745
if set_attrs:
27392746
self.chown(tarinfo, targetpath, numeric_owner)
27402747
if not tarinfo.issym():

‎Lib/test/test_tarfile.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4883,6 +4883,33 @@ def test_sneaky_hardlink_fallback_deep(self):
48834883
self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
48844884
self.expect_file("s", symlink_to=os.path.join('..', 'escape'))
48854885

4886+
@symlink_test
4887+
def test_sneaky_hardlink_fallback_attrs(self):
4888+
# Test that when a hardlink is extracted as a copy of a symlink
4889+
# member, the hardlink's attributes are not applied to the file
4890+
# the symlink points to.
4891+
with ArchiveMaker() as arc:
4892+
arc.add("a/s", symlink_to=os.path.join("..", "flag"))
4893+
arc.add("s", hardlink_to=os.path.join("a", "s"),
4894+
mode='-rwxr-xr-x', mtime=86400, uid=1337, gid=1337)
4895+
4896+
for filter in 'tar', 'fully_trusted':
4897+
with (
4898+
self.subTest(filter),
4899+
unittest.mock.patch("os.chown", create=True) as mock_chown,
4900+
self.check_context(arc.open(), filter) as cc,
4901+
):
4902+
if not os_helper.can_symlink():
4903+
self.expect_file("a/")
4904+
else:
4905+
self.expect_file("a/s", symlink_to=os.path.join('..', 'flag'))
4906+
self.expect_file("s", symlink_to=os.path.join('..', 'flag'))
4907+
st = (cc.outerdir / 'flag').stat()
4908+
self.assertNotEqual(st.st_mtime, 86400)
4909+
if sys.platform != "win32":
4910+
self.assertNotEqual(st.st_mode & 0o777, 0o755)
4911+
mock_chown.assert_not_called()
4912+
48864913
@symlink_test
48874914
@os_helper.skip_unless_hardlink
48884915
def test_sneaky_hardlink_relocation(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :meth:`tarfile.TarFile.extractall` and :meth:`tarfile.TarFile.extract`
2+
changing the mode, modification time and owner of a file outside the
3+
destination directory when a hard link member is extracted as a copy of a
4+
symbolic link member, for example with the ``'tar'`` filter.

0 commit comments

Comments
 (0)