https://github.com/python/cpython/commit/c66df4e70435d257fd488b35ea129c6f317433a8
commit: c66df4e70435d257fd488b35ea129c6f317433a8
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-09-30T15:55:31+01:00
summary:

[3.14] gh-157190: Follow symlinks when extracting `tarfile` hard links 
(GH-157191) (#157261)

(cherry picked from commit b38be2e6cf9d989075ab73412c63e003ebad4ff3)

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

files:
M Lib/tarfile.py
M Lib/test/test_tarfile.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index f226a6ee4a5709..4e39721e7480a4 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2801,7 +2801,11 @@ def makelink_with_filter(self, tarinfo, targetpath,
                     if os.path.lexists(targetpath):
                         # Avoid FileExistsError on following os.link.
                         os.unlink(targetpath)
-                    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 its 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/test_tarfile.py b/Lib/test/test_tarfile.py
index 92954cc8c65294..932415daa3665d 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -4485,6 +4485,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)

_______________________________________________
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