summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/python/python3-setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-devtools/python/python3-setuptools')
-rw-r--r--meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch353
1 files changed, 353 insertions, 0 deletions
diff --git a/meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch b/meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch
new file mode 100644
index 0000000000..958ddf559b
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch
@@ -0,0 +1,353 @@
1From 88807c7062788254f654ea8c03427adc859321f0 Mon Sep 17 00:00:00 2001
2From: Jason R. Coombs <jaraco@jaraco.com>
3Date: Mon Apr 29 20:01:38 2024 -0400
4Subject: [PATCH] Merge pull request #4332 from pypa/debt/package-index-vcs
5
6Modernize package_index VCS handling
7
8Source: https://git.launchpad.net/ubuntu/+source/setuptools/tree/debian/patches/CVE-2024-6345.patch?h=applied/ubuntu/jammy-devel
9
10CVE: CVE-2024-6345
11
12Upstream-Status: Backport [https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0]
13
14Note: Cannot do exact upstream patch backport as the code changed.
15
16Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
17---
18 setup.cfg | 1 +
19 setuptools/package_index.py | 145 +++++++++++++++-----------
20 setuptools/tests/test_packageindex.py | 78 +++++++-------
21 3 files changed, 123 insertions(+), 101 deletions(-)
22
23diff --git a/setup.cfg b/setup.cfg
24index 0bc0101..b8585d7 100644
25--- a/setup.cfg
26+++ b/setup.cfg
27@@ -56,6 +56,7 @@ testing =
28 jaraco.envs>=2.2
29 pytest-xdist
30 sphinx
31+ pytest-subprocess
32 jaraco.path>=3.2.0
33 docs =
34 sphinx
35diff --git a/setuptools/package_index.py b/setuptools/package_index.py
36index e93fcc6..3a893df 100644
37--- a/setuptools/package_index.py
38+++ b/setuptools/package_index.py
39@@ -1,5 +1,6 @@
40 """PyPI and direct package downloading"""
41 import sys
42+import subprocess
43 import os
44 import re
45 import io
46@@ -566,7 +567,7 @@ class PackageIndex(Environment):
47 scheme = URL_SCHEME(spec)
48 if scheme:
49 # It's a url, download it to tmpdir
50- found = self._download_url(scheme.group(1), spec, tmpdir)
51+ found = self._download_url(spec, tmpdir)
52 base, fragment = egg_info_for_url(spec)
53 if base.endswith('.py'):
54 found = self.gen_setup(found, fragment, tmpdir)
55@@ -785,7 +786,7 @@ class PackageIndex(Environment):
56 raise DistutilsError("Download error for %s: %s"
57 % (url, v)) from v
58
59- def _download_url(self, scheme, url, tmpdir):
60+ def _download_url(self, url, tmpdir):
61 # Determine download filename
62 #
63 name, fragment = egg_info_for_url(url)
64@@ -800,19 +801,57 @@ class PackageIndex(Environment):
65
66 filename = os.path.join(tmpdir, name)
67
68- # Download the file
69- #
70- if scheme == 'svn' or scheme.startswith('svn+'):
71- return self._download_svn(url, filename)
72- elif scheme == 'git' or scheme.startswith('git+'):
73- return self._download_git(url, filename)
74- elif scheme.startswith('hg+'):
75- return self._download_hg(url, filename)
76- elif scheme == 'file':
77- return urllib.request.url2pathname(urllib.parse.urlparse(url)[2])
78- else:
79- self.url_ok(url, True) # raises error if not allowed
80- return self._attempt_download(url, filename)
81+ return self._download_vcs(url, filename) or self._download_other(url, filename)
82+
83+ @staticmethod
84+ def _resolve_vcs(url):
85+ """
86+ >>> rvcs = PackageIndex._resolve_vcs
87+ >>> rvcs('git+http://foo/bar')
88+ 'git'
89+ >>> rvcs('hg+https://foo/bar')
90+ 'hg'
91+ >>> rvcs('git:myhost')
92+ 'git'
93+ >>> rvcs('hg:myhost')
94+ >>> rvcs('http://foo/bar')
95+ """
96+ scheme = urllib.parse.urlsplit(url).scheme
97+ pre, sep, post = scheme.partition('+')
98+ # svn and git have their own protocol; hg does not
99+ allowed = set(['svn', 'git'] + ['hg'] * bool(sep))
100+ return next(iter({pre} & allowed), None)
101+
102+ def _download_vcs(self, url, spec_filename):
103+ vcs = self._resolve_vcs(url)
104+ if not vcs:
105+ return
106+ if vcs == 'svn':
107+ return self._download_svn(url, spec_filename)
108+
109+ filename, _, _ = spec_filename.partition('#')
110+ url, rev = self._vcs_split_rev_from_url(url)
111+
112+ self.info(f"Doing {vcs} clone from {url} to {filename}")
113+ subprocess.check_call([vcs, 'clone', '--quiet', url, filename])
114+
115+ co_commands = dict(
116+ git=[vcs, '-C', filename, 'checkout', '--quiet', rev],
117+ hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'],
118+ )
119+ if rev is not None:
120+ self.info(f"Checking out {rev}")
121+ subprocess.check_call(co_commands[vcs])
122+
123+ return filename
124+
125+ def _download_other(self, url, filename):
126+ scheme = urllib.parse.urlsplit(url).scheme
127+ if scheme == 'file': # pragma: no cover
128+ return urllib.request.url2pathname(urllib.parse.urlparse(url).path)
129+ # raise error if not allowed
130+ self.url_ok(url, True)
131+ return self._attempt_download(url, filename)
132
133 def scan_url(self, url):
134 self.process_url(url, True)
135@@ -842,7 +881,7 @@ class PackageIndex(Environment):
136 def _download_svn(self, url, filename):
137 warnings.warn("SVN download support is deprecated", UserWarning)
138 url = url.split('#', 1)[0] # remove any fragment for svn's sake
139- creds = ''
140+ creds = []
141 if url.lower().startswith('svn:') and '@' in url:
142 scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
143 if not netloc and path.startswith('//') and '/' in path[2:]:
144@@ -851,65 +890,49 @@ class PackageIndex(Environment):
145 if auth:
146 if ':' in auth:
147 user, pw = auth.split(':', 1)
148- creds = " --username=%s --password=%s" % (user, pw)
149+ creds.extend(["--username", user, "--password", pw])
150 else:
151- creds = " --username=" + auth
152+ creds.extend(["--username", auth])
153 netloc = host
154 parts = scheme, netloc, url, p, q, f
155 url = urllib.parse.urlunparse(parts)
156 self.info("Doing subversion checkout from %s to %s", url, filename)
157- os.system("svn checkout%s -q %s %s" % (creds, url, filename))
158+ cmd = ["svn", "checkout", "-q"] + creds + [url, filename]
159+ subprocess.check_call(cmd)
160+
161 return filename
162
163 @staticmethod
164- def _vcs_split_rev_from_url(url, pop_prefix=False):
165- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
166+ def _vcs_split_rev_from_url(url):
167+ """
168+ Given a possible VCS URL, return a clean URL and resolved revision if any.
169+
170+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url
171+ >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools')
172+ ('https://github.com/pypa/setuptools', 'v69.0.0')
173+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
174+ ('https://github.com/pypa/setuptools', None)
175+ >>> vsrfu('http://foo/bar')
176+ ('http://foo/bar', None)
177+ """
178+ parts = urllib.parse.urlsplit(url)
179
180- scheme = scheme.split('+', 1)[-1]
181+ clean_scheme = parts.scheme.split('+', 1)[-1]
182
183 # Some fragment identification fails
184- path = path.split('#', 1)[0]
185-
186- rev = None
187- if '@' in path:
188- path, rev = path.rsplit('@', 1)
189+ no_fragment_path, _, _ = parts.path.partition('#')
190
191- # Also, discard fragment
192- url = urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
193+ pre, sep, post = no_fragment_path.rpartition('@')
194+ clean_path, rev = (pre, post) if sep else (post, None)
195
196- return url, rev
197-
198- def _download_git(self, url, filename):
199- filename = filename.split('#', 1)[0]
200- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
201-
202- self.info("Doing git clone from %s to %s", url, filename)
203- os.system("git clone --quiet %s %s" % (url, filename))
204-
205- if rev is not None:
206- self.info("Checking out %s", rev)
207- os.system("git -C %s checkout --quiet %s" % (
208- filename,
209- rev,
210- ))
211+ resolved = parts._replace(
212+ scheme=clean_scheme,
213+ path=clean_path,
214+ # discard the fragment
215+ fragment='',
216+ ).geturl()
217
218- return filename
219-
220- def _download_hg(self, url, filename):
221- filename = filename.split('#', 1)[0]
222- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
223-
224- self.info("Doing hg clone from %s to %s", url, filename)
225- os.system("hg clone --quiet %s %s" % (url, filename))
226-
227- if rev is not None:
228- self.info("Updating to %s", rev)
229- os.system("hg --cwd %s up -C -r %s -q" % (
230- filename,
231- rev,
232- ))
233-
234- return filename
235+ return resolved, rev
236
237 def debug(self, msg, *args):
238 log.debug(msg, *args)
239diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
240index 8e9435e..cc7e86c 100644
241--- a/setuptools/tests/test_packageindex.py
242+++ b/setuptools/tests/test_packageindex.py
243@@ -6,7 +6,6 @@ import urllib.request
244 import urllib.error
245 import http.client
246
247-import mock
248 import pytest
249
250 import setuptools.package_index
251@@ -193,61 +192,60 @@ class TestPackageIndex:
252 assert dists[0].version == ''
253 assert dists[1].version == vc
254
255- def test_download_git_with_rev(self, tmpdir):
256+ def test_download_git_with_rev(self, tmp_path, fp):
257 url = 'git+https://github.example/group/project@master#egg=foo'
258 index = setuptools.package_index.PackageIndex()
259
260- with mock.patch("os.system") as os_system_mock:
261- result = index.download(url, str(tmpdir))
262+ expected_dir = tmp_path / 'project@master'
263+ fp.register([
264+ 'git',
265+ 'clone',
266+ '--quiet',
267+ 'https://github.example/group/project',
268+ expected_dir,
269+ ])
270+ fp.register(['git', '-C', expected_dir, 'checkout', '--quiet', 'master'])
271
272- os_system_mock.assert_called()
273+ result = index.download(url, tmp_path)
274
275- expected_dir = str(tmpdir / 'project@master')
276- expected = (
277- 'git clone --quiet '
278- 'https://github.example/group/project {expected_dir}'
279- ).format(**locals())
280- first_call_args = os_system_mock.call_args_list[0][0]
281- assert first_call_args == (expected,)
282+ assert result == str(expected_dir)
283+ assert len(fp.calls) == 2
284
285- tmpl = 'git -C {expected_dir} checkout --quiet master'
286- expected = tmpl.format(**locals())
287- assert os_system_mock.call_args_list[1][0] == (expected,)
288- assert result == expected_dir
289-
290- def test_download_git_no_rev(self, tmpdir):
291+ def test_download_git_no_rev(self, tmp_path, fp):
292 url = 'git+https://github.example/group/project#egg=foo'
293 index = setuptools.package_index.PackageIndex()
294
295- with mock.patch("os.system") as os_system_mock:
296- result = index.download(url, str(tmpdir))
297-
298- os_system_mock.assert_called()
299+ expected_dir = tmp_path / 'project'
300+ fp.register([
301+ 'git',
302+ 'clone',
303+ '--quiet',
304+ 'https://github.example/group/project',
305+ expected_dir,
306+ ])
307+ result = index.download(url, tmp_path)
308
309- expected_dir = str(tmpdir / 'project')
310- expected = (
311- 'git clone --quiet '
312- 'https://github.example/group/project {expected_dir}'
313- ).format(**locals())
314- os_system_mock.assert_called_once_with(expected)
315+ assert result == str(expected_dir)
316+ assert len(fp.calls) == 1
317
318- def test_download_svn(self, tmpdir):
319+ def test_download_svn(self, tmp_path, fp):
320 url = 'svn+https://svn.example/project#egg=foo'
321 index = setuptools.package_index.PackageIndex()
322
323- with pytest.warns(UserWarning):
324- with mock.patch("os.system") as os_system_mock:
325- result = index.download(url, str(tmpdir))
326-
327- os_system_mock.assert_called()
328+ expected_dir = tmp_path / 'project'
329+ fp.register([
330+ 'svn',
331+ 'checkout',
332+ '-q',
333+ 'svn+https://svn.example/project',
334+ expected_dir,
335+ ])
336
337- expected_dir = str(tmpdir / 'project')
338- expected = (
339- 'svn checkout -q '
340- 'svn+https://svn.example/project {expected_dir}'
341- ).format(**locals())
342- os_system_mock.assert_called_once_with(expected)
343+ with pytest.warns(UserWarning, match="SVN download support is deprecated"):
344+ result = index.download(url, tmp_path)
345
346+ assert result == str(expected_dir)
347+ assert len(fp.calls) == 1
348
349 class TestContentCheckers:
350 def test_md5(self):
351--
3522.40.0
353