summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSoumya Sambu <soumya.sambu@windriver.com>2025-04-25 04:50:26 +0000
committerSteve Sakoman <steve@sakoman.com>2025-05-02 08:12:41 -0700
commit8208d973b908a3d4b0162534b7168f4e22590e7e (patch)
treea49a5ca3a6e590d3bcfa90045600cfd07df0bc43
parent520ba611e6b5bfb592d2e57032493ac9dd57ccaf (diff)
downloadpoky-8208d973b908a3d4b0162534b7168f4e22590e7e.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 https://ubuntu.com/security/CVE-2024-6345 Upstream patch: https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0 (From OE-Core rev: 238c305ba2c513a070818de4b6ad4316b54050a7) 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.patch353
-rw-r--r--meta/recipes-devtools/python/python3-setuptools_59.5.0.bb1
2 files changed, 354 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
diff --git a/meta/recipes-devtools/python/python3-setuptools_59.5.0.bb b/meta/recipes-devtools/python/python3-setuptools_59.5.0.bb
index 5f2676a04a..0c0f1e9d81 100644
--- a/meta/recipes-devtools/python/python3-setuptools_59.5.0.bb
+++ b/meta/recipes-devtools/python/python3-setuptools_59.5.0.bb
@@ -12,6 +12,7 @@ SRC_URI += "\
12 file://0001-change-shebang-to-python3.patch \ 12 file://0001-change-shebang-to-python3.patch \
13 file://0001-_distutils-sysconfig-append-STAGING_LIBDIR-python-sy.patch \ 13 file://0001-_distutils-sysconfig-append-STAGING_LIBDIR-python-sy.patch \
14 file://0001-Limit-the-amount-of-whitespace-to-search-backtrack.-.patch \ 14 file://0001-Limit-the-amount-of-whitespace-to-search-backtrack.-.patch \
15 file://CVE-2024-6345.patch \
15" 16"
16 17
17SRC_URI[sha256sum] = "d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" 18SRC_URI[sha256sum] = "d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0"