Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
gh-151558: Fix symlink escape via tarfile hardlink-extraction fallb…
…ack (GH-151559)

(cherry picked from commit 27dd970)

Co-authored-by: Stan Ulbrych <stan@python.org>
  • Loading branch information
StanFromIreland authored and miss-islington committed Jun 23, 2026
commit d0ba16dce6df2f68e8968699d5c247eddeef9960
3 changes: 3 additions & 0 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2677,6 +2677,9 @@ def makelink_with_filter(self, tarinfo, targetpath,
"makelink_with_filter: if filter_function is not None, "
+ "extraction_root must also not be None")
try:
filter_function(
unfiltered.replace(name=tarinfo.name, deep=False),
extraction_root)
filtered = filter_function(unfiltered, extraction_root)
except _FILTER_ERRORS as cause:
raise LinkFallbackError(tarinfo, unfiltered.name) from cause
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4182,6 +4182,30 @@ def test_sneaky_hardlink_fallback(self):
self.expect_file("boom", symlink_to='../../link_here')
self.expect_file("c", symlink_to='b')

@symlink_test
def test_sneaky_hardlink_fallback_deep(self):
# (CVE-2026-11940)
with ArchiveMaker() as arc:
arc.add("a/b/s", symlink_to=os.path.join("..", "escape"))
arc.add("s", hardlink_to=os.path.join("a", "b", "s"))

with self.check_context(arc.open(), 'data'):
e = self.expect_exception(
tarfile.LinkFallbackError,
"link 's' would be extracted as a copy of "
+ "'a/b/s', which was rejected")
self.assertIsInstance(e.__cause__,
tarfile.LinkOutsideDestinationError)

for filter in 'tar', 'fully_trusted':
with self.subTest(filter), self.check_context(arc.open(), filter):
if not os_helper.can_symlink():
self.expect_file("a/")
self.expect_file("a/b/")
else:
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_exfiltration_via_symlink(self):
# (CVE-2025-4138)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed an vulnerability in the :mod:`tarfile` ``data`` and ``tar`` extraction
filters where crafted archives could create a symlink pointing outside the
destination directory. This was a bypass of :cve:`2025-4330`.
Loading