https://github.com/python/cpython/commit/97688346ada2df3e5b9c279348862c3d64ab0823
commit: 97688346ada2df3e5b9c279348862c3d64ab0823
branch: main
author: Stan Ulbrych <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-08-19T09:52:01+01:00
summary:
gh-155999: `tarfile`: handle a member that leaves the destination but comes
back (#156000)
files:
A Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst
M Doc/library/tarfile.rst
M Lib/tarfile.py
M Lib/test/test_tarfile.py
diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
index f19038d837bb52..0d8ce6e497915f 100644
--- a/Doc/library/tarfile.rst
+++ b/Doc/library/tarfile.rst
@@ -1112,6 +1112,10 @@ reused in custom filters:
paths (in case the name is absolute
even after stripping slashes, e.g. ``C:/foo`` on Windows).
This raises :class:`~tarfile.AbsolutePathError`.
+ - Normalize filenames (:attr:`TarInfo.name`) that contain ``..`` components
+ using :func:`os.path.normpath`.
+ Note that this removes internal ``..`` components, which may change the
+ meaning of the name if it traverses symbolic links.
- :ref:`Refuse <tarfile-extraction-refuse>` to extract files whose absolute
path (after following symlinks) would end up outside the destination.
This raises :class:`~tarfile.OutsideDestinationError`.
@@ -1120,6 +1124,10 @@ reused in custom filters:
Return the modified ``TarInfo`` member.
+ .. versionchanged:: next
+
+ Filenames containing ``..`` components are now normalized.
+
.. function:: data_filter(member, path)
Implements the ``'data'`` filter.
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index cee21bfc6fe5aa..451302715329fe 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -833,6 +833,13 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
# For example, 'C:/foo' on Windows.
raise AbsolutePathError(member)
# Ensure we stay in the destination
+ if '..' in name.replace(os.sep, '/').split('/'):
+ # Directories are created from the name as given, so a name that
+ # leaves the destination part-way through would create them
+ # outside it even if the resolved path stays inside.
+ normalized = os.path.normpath(name)
+ if normalized != name:
+ name = new_attrs['name'] = normalized
target_path = os.path.realpath(os.path.join(dest_path, name),
strict=os.path.ALLOW_MISSING)
if os.path.commonpath([target_path, dest_path]) != dest_path:
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index c8c9b49fb6fc61..3899eac6be3b3a 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -4092,6 +4092,20 @@ def test_absolute(self):
tarfile.AbsolutePathError,
"""['"].*escaped.evil['"] has an absolute path""")
+ def test_parent_dir_out_and_back(self):
+ # Test a member that leaves the destination and comes back.
+ # The containment check looks at the resolved path, which stays
+ # inside, but the intermediate directories are created from the
+ # name as given, which does not.
+ with ArchiveMaker() as arc:
+ arc.add(f'../escaped.evil/../{self.destdir.name}/sub/file',
+ content='content')
+
+ for filter in 'tar', 'data':
+ with self.subTest(filter):
+ with self.check_context(arc.open(), filter):
+ self.expect_file('sub/file', content='content')
+
@symlink_test
def test_parent_symlink(self):
# Test interplaying symlinks
diff --git
a/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst
b/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst
new file mode 100644
index 00000000000000..59b725e55bbffd
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-08-13-13-08-11.gh-issue-155999.Xt4rWq.rst
@@ -0,0 +1,5 @@
+Fix the :mod:`tarfile` ``tar`` and ``data`` extraction filters creating
+directories outside the destination for members whose name leaves the
+destination and returns to it, such as ``../evil/../dest/sub/file``. The
+containment check used the resolved path, but intermediate directories were
+created from the name as given.
_______________________________________________
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]