https://github.com/python/cpython/commit/28f315486b3da0352b9a1de1c3c97f4127ba4771
commit: 28f315486b3da0352b9a1de1c3c97f4127ba4771
branch: 3.11
author: Russell Keith-Magee <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-30T00:40:33+01:00
summary:

[3.11] gh-157190: Fix tarfile `data`/`tar` filter bypass via hard link to a 
symlink (GH-157191) (#158455)

The backport to 3.13 and below includes a NEWS entry.
(cherry picked from commit b8f23e307097552eaea2604383a12ab280520d0d)

Co-authored-by: Stan Ulbrych <[email protected]>

files:
A Misc/NEWS.d/next/Security/2026-09-06-11-02-46.gh-issue-157190.tarhln.rst
M Lib/tarfile.py
M Lib/test/support/os_helper.py
M Lib/test/test_tarfile.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index f27a97030d22426..443f53e9b7386a7 100755
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2650,7 +2650,11 @@ def makelink_with_filter(self, tarinfo, targetpath,
                 return
             else:
                 if os.path.exists(tarinfo._link_target):
-                    os.link(tarinfo._link_target, targetpath)
+                    # Resolve the target so the hard link points to the file
+                    # itself. Otherwise os.link() may duplicate a symlink to a
+                    # shallower location, where it's relative target escapes 
the
+                    # destination directory. (CVE-2026-82049)
+                    os.link(os.path.realpath(tarinfo._link_target), targetpath)
                     return
         except symlink_exception:
             keyerror_to_extracterror = True
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index c1b2995ef37a12c..b7fc0c30864c4cf 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -9,6 +9,8 @@
 import unittest
 import warnings
 
+from test import support
+
 
 # Filename used for testing
 if os.name == 'java':
@@ -194,6 +196,23 @@ def skip_unless_symlink(test):
     return test if ok else unittest.skip(msg)(test)
 
 
+_can_hardlink = None
+
+def can_hardlink():
+    global _can_hardlink
+    if _can_hardlink is None:
+        # Android blocks hard links using SELinux
+        # (https://stackoverflow.com/q/32365690).
+        _can_hardlink = hasattr(os, "link") and not support.is_android
+    return _can_hardlink
+
+
+def skip_unless_hardlink(test):
+    ok = can_hardlink()
+    msg = "requires hardlink support"
+    return test if ok else unittest.skip(msg)(test)
+
+
 _can_xattr = None
 
 
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 865d44709dff559..1c4facb0bca2acd 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -4105,6 +4105,24 @@ def test_sneaky_hardlink_fallback_deep(self):
                     self.expect_file("a/b/s", symlink_to=os.path.join('..', 
'escape'))
                     self.expect_file("s", symlink_to=os.path.join('..', 
'escape'))
 
+    @symlink_test
+    @os_helper.skip_unless_hardlink
+    def test_sneaky_hardlink_relocation(self):
+        with ArchiveMaker() as arc:
+            arc.add("a/escape", content="decoy")
+            arc.add("a/b/s", symlink_to=os.path.join("..", "escape"))
+            arc.add("s", hardlink_to=os.path.join("a", "b", "s"))
+
+        for filter in 'data', 'tar':
+            with self.subTest(filter), self.check_context(arc.open(), filter):
+                self.expect_file("a/escape", content="decoy")
+                if os_helper.can_symlink():
+                    self.expect_file("a/b/s", symlink_to=os.path.join('..', 
'escape'))
+                else:
+                    self.expect_file("a/b/s", content="decoy")
+                self.expect_file("s", content="decoy")
+                self.assertFalse((self.destdir / "s").is_symlink())
+
     @symlink_test
     def test_exfiltration_via_symlink(self):
         # (CVE-2025-4138)
diff --git 
a/Misc/NEWS.d/next/Security/2026-09-06-11-02-46.gh-issue-157190.tarhln.rst 
b/Misc/NEWS.d/next/Security/2026-09-06-11-02-46.gh-issue-157190.tarhln.rst
new file mode 100644
index 000000000000000..c3aac4084c1859c
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-09-06-11-02-46.gh-issue-157190.tarhln.rst
@@ -0,0 +1,5 @@
+Fixed a vulnerability in the :mod:`tarfile` ``data`` and ``tar`` extraction
+filters where a crafted archive using a hard link to a symbolic link could
+change the permissions and modification time of a file outside the
+destination directory, and expose its contents inside the extracted tree.
+This addresses CVE 2026-82049.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to