Skip to content

Commit 0c85dc8

Browse files
[3.14] gh-160297: Fix tarfile setting attributes through a hard link fallback (GH-160298) (#161059)
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 e5f1477) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com>
1 parent c67d3db commit 0c85dc8

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
@@ -2707,6 +2707,13 @@ def _extract_member(self, tarinfo, targetpath, set_attrs=True,
27072707
else:
27082708
self.makefile(tarinfo, targetpath)
27092709

2710+
if tarinfo.islnk() and os.path.islink(targetpath):
2711+
# makelink_with_filter() fell back to extracting a copy of a
2712+
# symlink member, which already has its own attributes set.
2713+
# Setting the hard link's attributes here would follow the
2714+
# symlink, possibly to a file outside the destination directory.
2715+
set_attrs = False
2716+
27102717
if set_attrs:
27112718
self.chown(tarinfo, targetpath, numeric_owner)
27122719
if not tarinfo.issym():

‎Lib/test/test_tarfile.py‎

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

4529+
@symlink_test
4530+
def test_sneaky_hardlink_fallback_attrs(self):
4531+
# Test that when a hardlink is extracted as a copy of a symlink
4532+
# member, the hardlink's attributes are not applied to the file
4533+
# the symlink points to.
4534+
with ArchiveMaker() as arc:
4535+
arc.add("a/s", symlink_to=os.path.join("..", "flag"))
4536+
arc.add("s", hardlink_to=os.path.join("a", "s"),
4537+
mode='-rwxr-xr-x', mtime=86400, uid=1337, gid=1337)
4538+
4539+
for filter in 'tar', 'fully_trusted':
4540+
with (
4541+
self.subTest(filter),
4542+
unittest.mock.patch("os.chown", create=True) as mock_chown,
4543+
self.check_context(arc.open(), filter) as cc,
4544+
):
4545+
if not os_helper.can_symlink():
4546+
self.expect_file("a/")
4547+
else:
4548+
self.expect_file("a/s", symlink_to=os.path.join('..', 'flag'))
4549+
self.expect_file("s", symlink_to=os.path.join('..', 'flag'))
4550+
st = (cc.outerdir / 'flag').stat()
4551+
self.assertNotEqual(st.st_mtime, 86400)
4552+
if sys.platform != "win32":
4553+
self.assertNotEqual(st.st_mode & 0o777, 0o755)
4554+
mock_chown.assert_not_called()
4555+
45294556
@symlink_test
45304557
@os_helper.skip_unless_hardlink
45314558
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)