Please find attached the debdiff missing from the original report mail. Regards, Daniel
diff -Nru setuptools-66.1.1/debian/changelog setuptools-66.1.1/debian/changelog --- setuptools-66.1.1/debian/changelog 2023-01-27 07:49:44.000000000 +0100 +++ setuptools-66.1.1/debian/changelog 2024-12-31 01:08:15.000000000 +0100 @@ -1,3 +1,13 @@ +setuptools (66.1.1-1+deb12u1) bookworm; urgency=medium + + * Non-maintainer upload by the Debian LTS team. + * debian/patches/CVE-2024-6345.patch: Fix CVE-2024-6345. + - Replace the unsafe use of os.system to fix a possible remote code + execution by supplying malicious URLs in a package index or via the + command line. + + -- Daniel Leidert <dleid...@debian.org> Tue, 31 Dec 2024 01:08:15 +0100 + setuptools (66.1.1-1) unstable; urgency=medium * New upstream version. diff -Nru setuptools-66.1.1/debian/gbp.conf setuptools-66.1.1/debian/gbp.conf --- setuptools-66.1.1/debian/gbp.conf 1970-01-01 01:00:00.000000000 +0100 +++ setuptools-66.1.1/debian/gbp.conf 2024-12-31 01:08:15.000000000 +0100 @@ -0,0 +1,4 @@ +[DEFAULT] +upstream-branch = upstream/bookworm +debian-branch = debian/bookworm +pristine-tar = true diff -Nru setuptools-66.1.1/debian/.gitlab-ci.yml setuptools-66.1.1/debian/.gitlab-ci.yml --- setuptools-66.1.1/debian/.gitlab-ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ setuptools-66.1.1/debian/.gitlab-ci.yml 2024-12-31 01:08:15.000000000 +0100 @@ -0,0 +1,7 @@ +--- + +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml + +variables: + RELEASE: 'bookworm' diff -Nru setuptools-66.1.1/debian/patches/CVE-2024-6345.patch setuptools-66.1.1/debian/patches/CVE-2024-6345.patch --- setuptools-66.1.1/debian/patches/CVE-2024-6345.patch 1970-01-01 01:00:00.000000000 +0100 +++ setuptools-66.1.1/debian/patches/CVE-2024-6345.patch 2024-12-31 01:08:15.000000000 +0100 @@ -0,0 +1,296 @@ +From: "Jason R. Coombs" <jar...@jaraco.com> +Date: Mon, 29 Apr 2024 09:38:31 -0400 +Subject: [PATCH 01/10] .. [PATCH 10/10] Modernize package_index VCS handling + +The issue is a possible remote code execution by supplying malicious URLs in a +package index or via the command line. The issue boils down to unsafe use of +os.system. Because easy_install and package_index are deprecated, the attack +surface is smaller, but it's conceivable through social engineering or minor +compromise to a package index could grant remote access. The fix was released +in v70.0.0. + +Acked-By: Daniel Leidert <dleid...@debian.org> +Origin: https://github.com/pypa/setuptools/pull/4332 +Bug: https://github.com/pypa/setuptools/issues/4331 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2024-6345 +Bug-Freexian-Security: https://deb.freexian.com/extended-lts/tracker/CVE-2024-6345 +--- + setuptools/package_index.py | 145 +++++++++++++++++++--------------- + setuptools/tests/test_packageindex.py | 34 ++++---- + 2 files changed, 100 insertions(+), 79 deletions(-) + +diff --git a/setuptools/package_index.py b/setuptools/package_index.py +index bec4183..ea4d640 100644 +--- a/setuptools/package_index.py ++++ b/setuptools/package_index.py +@@ -1,6 +1,7 @@ + """PyPI and direct package downloading.""" + + import sys ++import subprocess + import os + import re + import io +@@ -586,7 +587,7 @@ class PackageIndex(Environment): + scheme = URL_SCHEME(spec) + if scheme: + # It's a url, download it to tmpdir +- found = self._download_url(scheme.group(1), spec, tmpdir) ++ found = self._download_url(spec, tmpdir) + base, fragment = egg_info_for_url(spec) + if base.endswith('.py'): + found = self.gen_setup(found, fragment, tmpdir) +@@ -813,7 +814,7 @@ class PackageIndex(Environment): + else: + raise DistutilsError("Download error for %s: %s" % (url, v)) from v + +- def _download_url(self, scheme, url, tmpdir): ++ def _download_url(self, url, tmpdir): + # Determine download filename + # + name, fragment = egg_info_for_url(url) +@@ -828,19 +829,58 @@ class PackageIndex(Environment): + + filename = os.path.join(tmpdir, name) + +- # Download the file +- # +- if scheme == 'svn' or scheme.startswith('svn+'): +- return self._download_svn(url, filename) +- elif scheme == 'git' or scheme.startswith('git+'): +- return self._download_git(url, filename) +- elif scheme.startswith('hg+'): +- return self._download_hg(url, filename) +- elif scheme == 'file': +- return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) +- else: +- self.url_ok(url, True) # raises error if not allowed +- return self._attempt_download(url, filename) ++ return self._download_vcs(url, filename) or self._download_other(url, filename) ++ ++ @staticmethod ++ def _resolve_vcs(url): ++ """ ++ >>> rvcs = PackageIndex._resolve_vcs ++ >>> rvcs('git+http://foo/bar') ++ 'git' ++ >>> rvcs('hg+https://foo/bar') ++ 'hg' ++ >>> rvcs('git:myhost') ++ 'git' ++ >>> rvcs('hg:myhost') ++ >>> rvcs('http://foo/bar') ++ """ ++ scheme = urllib.parse.urlsplit(url).scheme ++ pre, sep, post = scheme.partition('+') ++ # svn and git have their own protocol; hg does not ++ allowed = set(['svn', 'git'] + ['hg'] * bool(sep)) ++ return next(iter({pre} & allowed), None) ++ ++ ++ def _download_vcs(self, url, spec_filename): ++ vcs = self._resolve_vcs(url) ++ if not vcs: ++ return ++ if vcs == 'svn': ++ return self._download_svn(url, spec_filename) ++ ++ filename, _, _ = spec_filename.partition('#') ++ url, rev = self._vcs_split_rev_from_url(url) ++ ++ self.info(f"Doing {vcs} clone from {url} to {filename}") ++ subprocess.check_call([vcs, 'clone', '--quiet', url, filename]) ++ ++ co_commands = dict( ++ git=[vcs, '-C', filename, 'checkout', '--quiet', rev], ++ hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'], ++ ) ++ if rev is not None: ++ self.info(f"Checking out {rev}") ++ subprocess.check_call(co_commands[vcs]) ++ ++ return filename ++ ++ def _download_other(self, url, filename): ++ scheme = urllib.parse.urlsplit(url).scheme ++ if scheme == 'file': # pragma: no cover ++ return urllib.request.url2pathname(urllib.parse.urlparse(url).path) ++ # raise error if not allowed ++ self.url_ok(url, True) ++ return self._attempt_download(url, filename) + + def scan_url(self, url): + self.process_url(url, True) +@@ -885,65 +925,40 @@ class PackageIndex(Environment): + netloc = host + parts = scheme, netloc, url, p, q, f + url = urllib.parse.urlunparse(parts) +- self.info("Doing subversion checkout from %s to %s", url, filename) +- os.system("svn checkout%s -q %s %s" % (creds, url, filename)) ++ self.info(f"Doing subversion checkout from {url} to {filename}") ++ subprocess.check_call(['svn', 'checkout', creds, '-q', url, filename]) + return filename + + @staticmethod +- def _vcs_split_rev_from_url(url, pop_prefix=False): +- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) ++ def _vcs_split_rev_from_url(url): ++ """ ++ Given a possible VCS URL, return a clean URL and resolved revision if any. ++ >>> vsrfu = PackageIndex._vcs_split_rev_from_url ++ >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools') ++ ('https://github.com/pypa/setuptools', 'v69.0.0') ++ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools') ++ ('https://github.com/pypa/setuptools', None) ++ >>> vsrfu('http://foo/bar') ++ ('http://foo/bar', None) ++ """ ++ parts = urllib.parse.urlsplit(url) + +- scheme = scheme.split('+', 1)[-1] ++ clean_scheme = parts.scheme.split('+', 1)[-1] + + # Some fragment identification fails +- path = path.split('#', 1)[0] +- +- rev = None +- if '@' in path: +- path, rev = path.rsplit('@', 1) +- +- # Also, discard fragment +- url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) +- +- return url, rev +- +- def _download_git(self, url, filename): +- filename = filename.split('#', 1)[0] +- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) +- +- self.info("Doing git clone from %s to %s", url, filename) +- os.system("git clone --quiet %s %s" % (url, filename)) +- +- if rev is not None: +- self.info("Checking out %s", rev) +- os.system( +- "git -C %s checkout --quiet %s" +- % ( +- filename, +- rev, +- ) +- ) ++ no_fragment_path, _, _ = parts.path.partition('#') + +- return filename ++ pre, sep, post = no_fragment_path.rpartition('@') ++ clean_path, rev = (pre, post) if sep else (post, None) + +- def _download_hg(self, url, filename): +- filename = filename.split('#', 1)[0] +- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) ++ resolved = parts._replace( ++ scheme=clean_scheme, ++ path=clean_path, ++ # discard the fragment ++ fragment='', ++ ).geturl() + +- self.info("Doing hg clone from %s to %s", url, filename) +- os.system("hg clone --quiet %s %s" % (url, filename)) +- +- if rev is not None: +- self.info("Updating to %s", rev) +- os.system( +- "hg --cwd %s up -C -r %s -q" +- % ( +- filename, +- rev, +- ) +- ) +- +- return filename ++ return resolved, rev + + def debug(self, msg, *args): + log.debug(msg, *args) +diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py +index 8b5356d..f138d7e 100644 +--- a/setuptools/tests/test_packageindex.py ++++ b/setuptools/tests/test_packageindex.py +@@ -1,4 +1,5 @@ + import sys ++import subprocess + import os + import distutils.errors + import platform +@@ -190,20 +191,23 @@ class TestPackageIndex: + url = 'git+https://github.example/group/project@master#egg=foo' + index = setuptools.package_index.PackageIndex() + +- with mock.patch("os.system") as os_system_mock: ++ with mock.patch("subprocess.check_call") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project@master') +- expected = ( +- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' +- ).format(**locals()) ++ expected = [ ++ 'git', 'clone', '--quiet', ++ 'https://github.example/group/project', f'{expected_dir}' ++ ] + first_call_args = os_system_mock.call_args_list[0][0] + assert first_call_args == (expected,) + +- tmpl = 'git -C {expected_dir} checkout --quiet master' +- expected = tmpl.format(**locals()) ++ expected = [ ++ 'git', '-C', f'{expected_dir}', ++ 'checkout', '--quiet', 'master' ++ ] + assert os_system_mock.call_args_list[1][0] == (expected,) + assert result == expected_dir + +@@ -211,15 +215,16 @@ class TestPackageIndex: + url = 'git+https://github.example/group/project#egg=foo' + index = setuptools.package_index.PackageIndex() + +- with mock.patch("os.system") as os_system_mock: ++ with mock.patch("subprocess.check_call") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') +- expected = ( +- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' +- ).format(**locals()) ++ expected = [ ++ 'git', 'clone', '--quiet', ++ 'https://github.example/group/project', f'{expected_dir}' ++ ] + os_system_mock.assert_called_once_with(expected) + + def test_download_svn(self, tmpdir): +@@ -227,15 +232,16 @@ class TestPackageIndex: + index = setuptools.package_index.PackageIndex() + + with pytest.warns(UserWarning): +- with mock.patch("os.system") as os_system_mock: ++ with mock.patch("subprocess.check_call") as os_system_mock: + result = index.download(url, str(tmpdir)) + + os_system_mock.assert_called() + + expected_dir = str(tmpdir / 'project') +- expected = ( +- 'svn checkout -q ' 'svn+https://svn.example/project {expected_dir}' +- ).format(**locals()) ++ expected = [ ++ 'svn', 'checkout', '', '-q', ++ 'svn+https://svn.example/project', f'{expected_dir}' ++ ] + os_system_mock.assert_called_once_with(expected) + + diff -Nru setuptools-66.1.1/debian/patches/series setuptools-66.1.1/debian/patches/series --- setuptools-66.1.1/debian/patches/series 2022-09-14 16:54:08.000000000 +0200 +++ setuptools-66.1.1/debian/patches/series 2024-12-31 01:08:15.000000000 +0100 @@ -14,3 +14,4 @@ sphinx-theme.diff no-sphinx-custom-icons.diff no-sphinx-hoverxref.diff +CVE-2024-6345.patch
signature.asc
Description: This is a digitally signed message part