Skip to content

Commit 82dad81

Browse files
gpsheadmiss-islington
authored andcommitted
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 7440c35 commit 82dad81

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
@@ -2722,6 +2722,13 @@ def _extract_member(self, tarinfo, targetpath, set_attrs=True,
27222722
else:
27232723
self.makefile(tarinfo, targetpath)
27242724

2725+
if tarinfo.islnk() and os.path.islink(targetpath):
2726+
# makelink_with_filter() fell back to extracting a copy of a
2727+
# symlink member, which already has its own attributes set.
2728+
# Setting the hard link's attributes here would follow the
2729+
# symlink, possibly to a file outside the destination directory.
2730+
set_attrs = False
2731+
27252732
if set_attrs:
27262733
self.chown(tarinfo, targetpath, numeric_owner)
27272734
if not tarinfo.issym():

‎Lib/test/test_tarfile.py‎

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

4659+
@symlink_test
4660+
def test_sneaky_hardlink_fallback_attrs(self):
4661+
# Test that when a hardlink is extracted as a copy of a symlink
4662+
# member, the hardlink's attributes are not applied to the file
4663+
# the symlink points to.
4664+
with ArchiveMaker() as arc:
4665+
arc.add("a/s", symlink_to=os.path.join("..", "flag"))
4666+
arc.add("s", hardlink_to=os.path.join("a", "s"),
4667+
mode='-rwxr-xr-x', mtime=86400, uid=1337, gid=1337)
4668+
4669+
for filter in 'tar', 'fully_trusted':
4670+
with (
4671+
self.subTest(filter),
4672+
unittest.mock.patch("os.chown", create=True) as mock_chown,
4673+
self.check_context(arc.open(), filter) as cc,
4674+
):
4675+
if not os_helper.can_symlink():
4676+
self.expect_file("a/")
4677+
else:
4678+
self.expect_file("a/s", symlink_to=os.path.join('..', 'flag'))
4679+
self.expect_file("s", symlink_to=os.path.join('..', 'flag'))
4680+
st = (cc.outerdir / 'flag').stat()
4681+
self.assertNotEqual(st.st_mtime, 86400)
4682+
if sys.platform != "win32":
4683+
self.assertNotEqual(st.st_mode & 0o777, 0o755)
4684+
mock_chown.assert_not_called()
4685+
46594686
@symlink_test
46604687
@os_helper.skip_unless_hardlink
46614688
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)