Skip to content

Commit cccc7ec

Browse files
[3.15] gh-157190: Follow symlinks when extracting tarfile hard links (GH-157191) (GH-157262)
(cherry picked from commit b38be2e) Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent ff3b98c commit cccc7ec

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

‎Lib/tarfile.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2823,7 +2823,11 @@ def makelink_with_filter(self, tarinfo, targetpath,
28232823
if os.path.lexists(targetpath):
28242824
# Avoid FileExistsError on following os.link.
28252825
os.unlink(targetpath)
2826-
os.link(tarinfo._link_target, targetpath)
2826+
# Resolve the target so the hard link points to the file
2827+
# itself. Otherwise os.link() may duplicate a symlink to a
2828+
# shallower location, where its relative target escapes the
2829+
# destination directory. (CVE-2026-82049)
2830+
os.link(os.path.realpath(tarinfo._link_target), targetpath)
28272831
return
28282832
except symlink_exception:
28292833
keyerror_to_extracterror = True

‎Lib/test/test_tarfile.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4547,6 +4547,24 @@ def test_sneaky_hardlink_fallback_deep(self):
45474547
self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
45484548
self.expect_file("s", symlink_to=os.path.join('..', 'escape'))
45494549

4550+
@symlink_test
4551+
@os_helper.skip_unless_hardlink
4552+
def test_sneaky_hardlink_relocation(self):
4553+
with ArchiveMaker() as arc:
4554+
arc.add("a/escape", content="decoy")
4555+
arc.add("a/b/s", symlink_to=os.path.join("..", "escape"))
4556+
arc.add("s", hardlink_to=os.path.join("a", "b", "s"))
4557+
4558+
for filter in 'data', 'tar':
4559+
with self.subTest(filter), self.check_context(arc.open(), filter):
4560+
self.expect_file("a/escape", content="decoy")
4561+
if os_helper.can_symlink():
4562+
self.expect_file("a/b/s", symlink_to=os.path.join('..', 'escape'))
4563+
else:
4564+
self.expect_file("a/b/s", content="decoy")
4565+
self.expect_file("s", content="decoy")
4566+
self.assertFalse((self.destdir / "s").is_symlink())
4567+
45504568
@symlink_test
45514569
def test_exfiltration_via_symlink(self):
45524570
# (CVE-2025-4138)

0 commit comments

Comments
 (0)