Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,13 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
# Ensure we stay in the destination
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:
try:
outside_destination = (os.path.commonpath([target_path, dest_path])
!= dest_path)
except ValueError:
# commonpath() raises for paths on different drives on Windows.
outside_destination = True
if outside_destination:
raise OutsideDestinationError(member, target_path)
# Limit permissions (no high bits, and go-w)
mode = member.mode
Expand Down Expand Up @@ -890,7 +896,13 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
target_path = os.path.join(dest_path, normalized)
target_path = os.path.realpath(target_path,
strict=os.path.ALLOW_MISSING)
if os.path.commonpath([target_path, dest_path]) != dest_path:
try:
outside_destination = (
os.path.commonpath([target_path, dest_path]) != dest_path)
except ValueError:
# commonpath() raises for paths on different drives on Windows.
outside_destination = True
if outside_destination:
raise LinkOutsideDestinationError(member, target_path)
return new_attrs

Expand Down
50 changes: 50 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3993,6 +3993,14 @@ def expect_exception(self, exc_type, message_re='.'):
self.reraise_exception = False
return self.raised_exception

def _make_drive_relative_path(self, name):
dest_drive = os.path.splitdrive(os.path.realpath(
self.destdir, strict=os.path.ALLOW_MISSING))[0]
for drive in "ZYXWVUTSRQPONMLKJIHGFEDCBA":
if drive.upper() != dest_drive.rstrip(":").upper():
return f"{drive}:{name}"
raise AssertionError("could not find a different drive")

def test_benign_file(self):
with ArchiveMaker() as arc:
arc.add('benign.txt')
Expand Down Expand Up @@ -4025,6 +4033,26 @@ def test_absolute(self):
tarfile.AbsolutePathError,
"""['"].*escaped.evil['"] has an absolute path""")

@unittest.skipIf(os.name != 'nt', 'Windows-specific drive-relative path')
def test_drive_relative_member_path(self):
# Drive-relative paths such as "Z:evil" are not absolute, but
# ntpath.commonpath() raises ValueError for them.
member_name = self._make_drive_relative_path('evil')
with ArchiveMaker() as arc:
arc.add('before')
arc.add(member_name)
arc.add('after')

for filter in 'tar', 'data':
with self.subTest(filter=filter, errorlevel=1):
with self.check_context(arc.open(errorlevel=1), filter):
self.expect_exception(tarfile.OutsideDestinationError)

with self.subTest(filter=filter, errorlevel=0):
with self.check_context(arc.open(errorlevel=0), filter):
self.expect_file('before')
self.expect_file('after')

@symlink_test
def test_parent_symlink(self):
# Test interplaying symlinks
Expand Down Expand Up @@ -4281,6 +4309,28 @@ def test_absolute_hardlink(self):
tarfile.AbsoluteLinkError,
"'parent' is a link to an absolute path")

@unittest.skipIf(os.name != 'nt', 'Windows-specific drive-relative path')
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
def test_data_filter_drive_relative_link_path(self, link_type):
linkname = self._make_drive_relative_path('evil')
link_kwargs = {}
if link_type == tarfile.SYMTYPE:
link_kwargs['symlink_to'] = linkname
else:
link_kwargs['hardlink_to'] = linkname

with ArchiveMaker() as arc:
arc.add('before')
arc.add('link', **link_kwargs)
arc.add('after')

with self.check_context(arc.open(errorlevel=1), 'data'):
self.expect_exception(tarfile.LinkOutsideDestinationError)

with self.check_context(arc.open(errorlevel=0), 'data'):
self.expect_file('before')
self.expect_file('after')

@symlink_test
def test_sly_relative0(self):
# Inspired by 'relative0' in jwilk/traversal-archives
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS.d/next/Library/2026-07-31-16-30-00.bpo-0.xz7oaj.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
On Windows, :mod:`tarfile` extraction filters now report drive-relative
member names and link targets using structured filter errors instead of
allowing :exc:`ValueError` from :func:`os.path.commonpath` to escape.
Loading