From 8947727186fff1897329325d690af832a5d38e4a 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) (cherry picked from commit 27dd970bf6b17ebca7c8ed486a40ab043ed7af8f) Co-authored-by: Stan Ulbrych --- Lib/tarfile.py | 3 +++ Lib/test/test_tarfile.py | 24 +++++++++++++++++++ ...-06-10-13-08-19.gh-issue-151558.mL74i2.rst | 3 +++ 3 files changed, 30 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst diff --git a/Lib/tarfile.py b/Lib/tarfile.py index e2d9f9e6c61b31..269516aded1dbc 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2663,6 +2663,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 diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 11066c005629c2..c859f3fa1c1f57 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -3998,6 +3998,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) diff --git a/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst new file mode 100644 index 00000000000000..74459d5680e21a --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst @@ -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`. From 9fe3c36bca0fbc0f715c80b1b904c66a0d071a1c Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 23 Jun 2026 14:49:41 +0100 Subject: [PATCH 2/2] Can't use `:cve:` on 3.11 --- .../Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst index 74459d5680e21a..575081600f8bdd 100644 --- a/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst +++ b/Misc/NEWS.d/next/Security/2026-06-10-13-08-19.gh-issue-151558.mL74i2.rst @@ -1,3 +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`. +destination directory. This was a bypass of CVE-2025-4330.