Skip to content

Commit b70102d

Browse files
committed
Add a check to ensure the name resolves relative to the tmpdir
Closes pypa#4946: Security Fix for CVE-2025-47273 From 250a6d1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" <[email protected]> Date: Sat, 19 Apr 2025 13:03:47 -0400 --- Adapted to integrate into 68.0.0.2 this required the creation of the staticmethod _resolve_download_filename which was not present in this version.
1 parent 24189c3 commit b70102d

File tree

1 file changed

+30
-4
lines changed

1 file changed

+30
-4
lines changed

setuptools/package_index.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -801,10 +801,25 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
801801
else:
802802
raise DistutilsError("Download error for %s: %s" % (url, v)) from v
803803

804-
def _download_url(self, url, tmpdir):
805-
# Determine download filename
806-
#
807-
name, fragment = egg_info_for_url(url)
804+
@staticmethod
805+
def _resolve_download_filename(url, tmpdir):
806+
"""
807+
>>> import pathlib
808+
>>> du = PackageIndex._resolve_download_filename
809+
>>> root = getfixture('tmp_path')
810+
>>> url = 'https://files.pythonhosted.org/packages/a9/5a/0db.../setuptools-78.1.0.tar.gz'
811+
>>> str(pathlib.Path(du(url, root)).relative_to(root))
812+
'setuptools-78.1.0.tar.gz'
813+
814+
Ensures the target is always in tmpdir.
815+
816+
>>> url = 'https://anyhost/%2fhome%2fuser%2f.ssh%2fauthorized_keys'
817+
>>> du(url, root)
818+
Traceback (most recent call last):
819+
...
820+
ValueError: Invalid filename...
821+
"""
822+
name, _fragment = egg_info_for_url(url)
808823
if name:
809824
while '..' in name:
810825
name = name.replace('..', '.').replace('\\', '_')
@@ -816,6 +831,17 @@ def _download_url(self, url, tmpdir):
816831

817832
filename = os.path.join(tmpdir, name)
818833

834+
# ensure path resolves within the tmpdir
835+
if not filename.startswith(str(tmpdir)):
836+
raise ValueError(f"Invalid filename {filename}")
837+
838+
return filename
839+
840+
def _download_url(self, url, tmpdir):
841+
"""
842+
Determine the download filename.
843+
"""
844+
filename = self._resolve_download_filename(url, tmpdir)
819845
return self._download_vcs(url, filename) or self._download_other(url, filename)
820846

821847
@staticmethod

0 commit comments

Comments
 (0)