From 8db3857331be8920c65f641c2d57e4e51f71d6bc Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 23 Jun 2026 14:31:38 +0100 Subject: [PATCH 1/2] gh-151558: Fix symlink escape via `tarfile` hardlink-extraction fallback (GH-151559) --- Lib/tarfile.py | 3 +++ Lib/test/test_tarfile.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index fa3f922647..191fd38cba 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2668,6 +2668,9 @@ class TarFile(object): "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 diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 8b9aea223c..34d0cff17b 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -3854,6 +3854,31 @@ class TestExtractionFilters(unittest.TestCase): 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 support.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) # Test changing symlinks that result in a symlink pointing outside From b803c1f883f01db47a9ae28a79c5caedb859cfce Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Thu, 6 Aug 2026 09:39:24 +0000 Subject: [PATCH 2/2] Fix: replace undefined symlink_test decorator with support.skip_unless_symlink for Python 3.9 --- Lib/test/test_tarfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 34d0cff17b..e45ca141c9 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -3854,7 +3854,7 @@ class TestExtractionFilters(unittest.TestCase): self.expect_file("boom", symlink_to='../../link_here') self.expect_file("c", symlink_to='b') - @symlink_test + @support.skip_unless_symlink def test_sneaky_hardlink_fallback_deep(self): # (CVE-2026-11940) with ArchiveMaker() as arc: @@ -3878,7 +3878,6 @@ class TestExtractionFilters(unittest.TestCase): 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) # Test changing symlinks that result in a symlink pointing outside