summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSoumya Sambu <soumya.sambu@windriver.com>2024-09-01 17:17:26 +0000
committerSteve Sakoman <steve@sakoman.com>2024-09-09 06:08:10 -0700
commit67aa29393db111a67b64f3394a0c490c33946c02 (patch)
tree950ce8741e9947608ee72ef3cedaa10b500479bb
parent8637aa34f0009421eb6c51a93588fe879950e1f4 (diff)
downloadpoky-67aa29393db111a67b64f3394a0c490c33946c02.tar.gz
python3-setuptools: Fix CVE-2024-6345
A vulnerability in the package_index module of pypa/setuptools versions up to 69.1.1 allows for remote code execution via its download functions. These functions, which are used to download packages from URLs provided by users or retrieved from package index servers, are susceptible to code injection. If these functions are exposed to user-controlled inputs, such as package URLs, they can execute arbitrary commands on the system. The issue is fixed in version 70.0. References: https://nvd.nist.gov/vuln/detail/CVE-2024-6345 Upstream-patch: https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0 (From OE-Core rev: 468c5a4e12b9d38768b00151c55fd27b2b504f3b) Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch312
-rw-r--r--meta/recipes-devtools/python/python3-setuptools_69.1.1.bb4
2 files changed, 315 insertions, 1 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..ac520be74a
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-setuptools/CVE-2024-6345.patch
@@ -0,0 +1,312 @@
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
8CVE: CVE-2024-6345
9
10Upstream-Status: Backport [https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0]
11
12Signed-off-by: Soumya Sambu <soumya.sambu@windriver.com>
13---
14 setup.cfg | 1 +
15 setuptools/package_index.py | 145 ++++++++++++++------------
16 setuptools/tests/test_packageindex.py | 56 +++++-----
17 3 files changed, 106 insertions(+), 96 deletions(-)
18
19diff --git a/setup.cfg b/setup.cfg
20index edf9798..238d00a 100644
21--- a/setup.cfg
22+++ b/setup.cfg
23@@ -65,6 +65,7 @@ testing =
24 sys_platform != "cygwin"
25 jaraco.develop >= 7.21; python_version >= "3.9" and sys_platform != "cygwin"
26 pytest-home >= 0.5
27+ pytest-subprocess
28 testing-integration =
29 pytest
30 pytest-xdist
31diff --git a/setuptools/package_index.py b/setuptools/package_index.py
32index 271aa97..00a972d 100644
33--- a/setuptools/package_index.py
34+++ b/setuptools/package_index.py
35@@ -1,6 +1,7 @@
36 """PyPI and direct package downloading."""
37
38 import sys
39+import subprocess
40 import os
41 import re
42 import io
43@@ -585,7 +586,7 @@ class PackageIndex(Environment):
44 scheme = URL_SCHEME(spec)
45 if scheme:
46 # It's a url, download it to tmpdir
47- found = self._download_url(scheme.group(1), spec, tmpdir)
48+ found = self._download_url(spec, tmpdir)
49 base, fragment = egg_info_for_url(spec)
50 if base.endswith('.py'):
51 found = self.gen_setup(found, fragment, tmpdir)
52@@ -814,7 +815,7 @@ class PackageIndex(Environment):
53 else:
54 raise DistutilsError("Download error for %s: %s" % (url, v)) from v
55
56- def _download_url(self, scheme, url, tmpdir):
57+ def _download_url(self, url, tmpdir):
58 # Determine download filename
59 #
60 name, fragment = egg_info_for_url(url)
61@@ -829,19 +830,59 @@ class PackageIndex(Environment):
62
63 filename = os.path.join(tmpdir, name)
64
65- # Download the file
66- #
67- if scheme == 'svn' or scheme.startswith('svn+'):
68- return self._download_svn(url, filename)
69- elif scheme == 'git' or scheme.startswith('git+'):
70- return self._download_git(url, filename)
71- elif scheme.startswith('hg+'):
72- return self._download_hg(url, filename)
73- elif scheme == 'file':
74- return urllib.request.url2pathname(urllib.parse.urlparse(url)[2])
75- else:
76- self.url_ok(url, True) # raises error if not allowed
77- return self._attempt_download(url, filename)
78+ return self._download_vcs(url, filename) or self._download_other(url, filename)
79+
80+ @staticmethod
81+ def _resolve_vcs(url):
82+ """
83+ >>> rvcs = PackageIndex._resolve_vcs
84+ >>> rvcs('git+http://foo/bar')
85+ 'git'
86+ >>> rvcs('hg+https://foo/bar')
87+ 'hg'
88+ >>> rvcs('git:myhost')
89+ 'git'
90+ >>> rvcs('hg:myhost')
91+ >>> rvcs('http://foo/bar')
92+ """
93+ scheme = urllib.parse.urlsplit(url).scheme
94+ pre, sep, post = scheme.partition('+')
95+ # svn and git have their own protocol; hg does not
96+ allowed = set(['svn', 'git'] + ['hg'] * bool(sep))
97+ return next(iter({pre} & allowed), None)
98+
99+ def _download_vcs(self, url, spec_filename):
100+ vcs = self._resolve_vcs(url)
101+ if not vcs:
102+ return
103+ if vcs == 'svn':
104+ raise DistutilsError(
105+ f"Invalid config, SVN download is not supported: {url}"
106+ )
107+
108+ filename, _, _ = spec_filename.partition('#')
109+ url, rev = self._vcs_split_rev_from_url(url)
110+
111+ self.info(f"Doing {vcs} clone from {url} to {filename}")
112+ subprocess.check_call([vcs, 'clone', '--quiet', url, filename])
113+
114+ co_commands = dict(
115+ git=[vcs, '-C', filename, 'checkout', '--quiet', rev],
116+ hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'],
117+ )
118+ if rev is not None:
119+ self.info(f"Checking out {rev}")
120+ subprocess.check_call(co_commands[vcs])
121+
122+ return filename
123+
124+ def _download_other(self, url, filename):
125+ scheme = urllib.parse.urlsplit(url).scheme
126+ if scheme == 'file': # pragma: no cover
127+ return urllib.request.url2pathname(urllib.parse.urlparse(url).path)
128+ # raise error if not allowed
129+ self.url_ok(url, True)
130+ return self._attempt_download(url, filename)
131
132 def scan_url(self, url):
133 self.process_url(url, True)
134@@ -857,64 +898,36 @@ class PackageIndex(Environment):
135 os.unlink(filename)
136 raise DistutilsError(f"Unexpected HTML page found at {url}")
137
138- def _download_svn(self, url, _filename):
139- raise DistutilsError(f"Invalid config, SVN download is not supported: {url}")
140-
141 @staticmethod
142- def _vcs_split_rev_from_url(url, pop_prefix=False):
143- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
144+ def _vcs_split_rev_from_url(url):
145+ """
146+ Given a possible VCS URL, return a clean URL and resolved revision if any.
147+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url
148+ >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools')
149+ ('https://github.com/pypa/setuptools', 'v69.0.0')
150+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
151+ ('https://github.com/pypa/setuptools', None)
152+ >>> vsrfu('http://foo/bar')
153+ ('http://foo/bar', None)
154+ """
155+ parts = urllib.parse.urlsplit(url)
156
157- scheme = scheme.split('+', 1)[-1]
158+ clean_scheme = parts.scheme.split('+', 1)[-1]
159
160 # Some fragment identification fails
161- path = path.split('#', 1)[0]
162-
163- rev = None
164- if '@' in path:
165- path, rev = path.rsplit('@', 1)
166-
167- # Also, discard fragment
168- url = urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
169-
170- return url, rev
171-
172- def _download_git(self, url, filename):
173- filename = filename.split('#', 1)[0]
174- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
175-
176- self.info("Doing git clone from %s to %s", url, filename)
177- os.system("git clone --quiet %s %s" % (url, filename))
178-
179- if rev is not None:
180- self.info("Checking out %s", rev)
181- os.system(
182- "git -C %s checkout --quiet %s"
183- % (
184- filename,
185- rev,
186- )
187- )
188+ no_fragment_path, _, _ = parts.path.partition('#')
189
190- return filename
191+ pre, sep, post = no_fragment_path.rpartition('@')
192+ clean_path, rev = (pre, post) if sep else (post, None)
193
194- def _download_hg(self, url, filename):
195- filename = filename.split('#', 1)[0]
196- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
197+ resolved = parts._replace(
198+ scheme=clean_scheme,
199+ path=clean_path,
200+ # discard the fragment
201+ fragment='',
202+ ).geturl()
203
204- self.info("Doing hg clone from %s to %s", url, filename)
205- os.system("hg clone --quiet %s %s" % (url, filename))
206-
207- if rev is not None:
208- self.info("Updating to %s", rev)
209- os.system(
210- "hg --cwd %s up -C -r %s -q"
211- % (
212- filename,
213- rev,
214- )
215- )
216-
217- return filename
218+ return resolved, rev
219
220 def debug(self, msg, *args):
221 log.debug(msg, *args)
222diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
223index 41b9661..e4cd91a 100644
224--- a/setuptools/tests/test_packageindex.py
225+++ b/setuptools/tests/test_packageindex.py
226@@ -2,7 +2,6 @@ import distutils.errors
227 import urllib.request
228 import urllib.error
229 import http.client
230-from unittest import mock
231
232 import pytest
233
234@@ -171,49 +170,46 @@ class TestPackageIndex:
235 assert dists[0].version == ''
236 assert dists[1].version == vc
237
238- def test_download_git_with_rev(self, tmpdir):
239+ def test_download_git_with_rev(self, tmp_path, fp):
240 url = 'git+https://github.example/group/project@master#egg=foo'
241 index = setuptools.package_index.PackageIndex()
242
243- with mock.patch("os.system") as os_system_mock:
244- result = index.download(url, str(tmpdir))
245+ expected_dir = tmp_path / 'project@master'
246+ fp.register([
247+ 'git',
248+ 'clone',
249+ '--quiet',
250+ 'https://github.example/group/project',
251+ expected_dir,
252+ ])
253+ fp.register(['git', '-C', expected_dir, 'checkout', '--quiet', 'master'])
254
255- os_system_mock.assert_called()
256+ result = index.download(url, tmp_path)
257
258- expected_dir = str(tmpdir / 'project@master')
259- expected = (
260- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}'
261- ).format(**locals())
262- first_call_args = os_system_mock.call_args_list[0][0]
263- assert first_call_args == (expected,)
264+ assert result == str(expected_dir)
265+ assert len(fp.calls) == 2
266
267- tmpl = 'git -C {expected_dir} checkout --quiet master'
268- expected = tmpl.format(**locals())
269- assert os_system_mock.call_args_list[1][0] == (expected,)
270- assert result == expected_dir
271-
272- def test_download_git_no_rev(self, tmpdir):
273+ def test_download_git_no_rev(self, tmp_path, fp):
274 url = 'git+https://github.example/group/project#egg=foo'
275 index = setuptools.package_index.PackageIndex()
276
277- with mock.patch("os.system") as os_system_mock:
278- result = index.download(url, str(tmpdir))
279-
280- os_system_mock.assert_called()
281-
282- expected_dir = str(tmpdir / 'project')
283- expected = (
284- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}'
285- ).format(**locals())
286- os_system_mock.assert_called_once_with(expected)
287-
288- def test_download_svn(self, tmpdir):
289+ expected_dir = tmp_path / 'project'
290+ fp.register([
291+ 'git',
292+ 'clone',
293+ '--quiet',
294+ 'https://github.example/group/project',
295+ expected_dir,
296+ ])
297+ index.download(url, tmp_path)
298+
299+ def test_download_svn(self, tmp_path):
300 url = 'svn+https://svn.example/project#egg=foo'
301 index = setuptools.package_index.PackageIndex()
302
303 msg = r".*SVN download is not supported.*"
304 with pytest.raises(distutils.errors.DistutilsError, match=msg):
305- index.download(url, str(tmpdir))
306+ index.download(url, tmp_path)
307
308
309 class TestContentCheckers:
310--
3112.40.0
312
diff --git a/meta/recipes-devtools/python/python3-setuptools_69.1.1.bb b/meta/recipes-devtools/python/python3-setuptools_69.1.1.bb
index 67475b68eb..7b9b02059f 100644
--- a/meta/recipes-devtools/python/python3-setuptools_69.1.1.bb
+++ b/meta/recipes-devtools/python/python3-setuptools_69.1.1.bb
@@ -9,7 +9,9 @@ inherit pypi python_setuptools_build_meta
9SRC_URI:append:class-native = " file://0001-conditionally-do-not-fetch-code-by-easy_install.patch" 9SRC_URI:append:class-native = " file://0001-conditionally-do-not-fetch-code-by-easy_install.patch"
10 10
11SRC_URI += " \ 11SRC_URI += " \
12 file://0001-_distutils-sysconfig.py-make-it-possible-to-substite.patch" 12 file://0001-_distutils-sysconfig.py-make-it-possible-to-substite.patch \
13 file://CVE-2024-6345.patch \
14"
13 15
14SRC_URI[sha256sum] = "5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" 16SRC_URI[sha256sum] = "5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"
15 17