Skip to content

Commit e2b028d

Browse files
Patch python-pip for CVE-2026-6357
1 parent 64adae0 commit e2b028d

2 files changed

Lines changed: 376 additions & 1 deletion

File tree

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
From 4d3bacc72e85d03e3dd8c9fdb6e960774192c53c Mon Sep 17 00:00:00 2001
2+
From: AllSpark <allspark@microsoft.com>
3+
Date: Mon, 4 May 2026 03:26:56 +0000
4+
Subject: [PATCH] Backport split pip self-version check into fetch(before) and
5+
emit(after) phases; adjust context managers and tests; compute prompt based
6+
on cached/remote and installer; skip check in install when pip is target
7+
8+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
9+
Upstream-reference: AI Backport of https://github.com/pypa/pip/pull/13923.patch
10+
---
11+
src/pip/_internal/cli/base_command.py | 11 ++--
12+
src/pip/_internal/cli/index_command.py | 35 +++++++++---
13+
src/pip/_internal/commands/install.py | 6 ++
14+
src/pip/_internal/commands/list.py | 13 +++--
15+
src/pip/_internal/self_outdated_check.py | 73 ++++++++++++------------
16+
tests/unit/test_base_command.py | 8 +--
17+
tests/unit/test_commands.py | 9 ++-
18+
7 files changed, 93 insertions(+), 62 deletions(-)
19+
20+
diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py
21+
index bc1ab65..4d81124 100644
22+
--- a/src/pip/_internal/cli/base_command.py
23+
+++ b/src/pip/_internal/cli/base_command.py
24+
@@ -6,8 +6,9 @@ import optparse
25+
import os
26+
import sys
27+
import traceback
28+
+import contextlib
29+
from optparse import Values
30+
-from typing import List, Optional, Tuple
31+
+from typing import List, Optional, Tuple, Iterator
32+
33+
from pip._vendor.rich import reconfigure
34+
from pip._vendor.rich import traceback as rich_traceback
35+
@@ -78,7 +79,8 @@ class Command(CommandContextMixIn):
36+
def add_options(self) -> None:
37+
pass
38+
39+
- def handle_pip_version_check(self, options: Values) -> None:
40+
+ @contextlib.contextmanager
41+
+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]:
42+
"""
43+
This is a no-op so that commands by default do not do the pip version
44+
check.
45+
@@ -86,16 +88,15 @@ class Command(CommandContextMixIn):
46+
# Make sure we do the pip version check if the index_group options
47+
# are present.
48+
assert not hasattr(options, "no_index")
49+
+ yield
50+
51+
def run(self, options: Values, args: List[str]) -> int:
52+
raise NotImplementedError
53+
54+
def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int:
55+
def _inner_run() -> int:
56+
- try:
57+
+ with self.pip_version_check(options, args):
58+
return self.run(options, args)
59+
- finally:
60+
- self.handle_pip_version_check(options)
61+
62+
if options.debug_mode:
63+
rich_traceback.install(show_locals=True)
64+
diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py
65+
index 226f8da..930f23a 100644
66+
--- a/src/pip/_internal/cli/index_command.py
67+
+++ b/src/pip/_internal/cli/index_command.py
68+
@@ -9,8 +9,12 @@ so commands which don't always hit the network (e.g. list w/o --outdated or
69+
import logging
70+
import os
71+
import sys
72+
+import contextlib
73+
from optparse import Values
74+
-from typing import TYPE_CHECKING, List, Optional
75+
+from typing import TYPE_CHECKING, List, Optional, Iterator
76+
+
77+
+if TYPE_CHECKING:
78+
+ from pip._internal.self_outdated_check import UpgradePrompt
79+
80+
from pip._vendor import certifi
81+
82+
@@ -131,10 +135,16 @@ class SessionCommandMixin(CommandContextMixIn):
83+
return session
84+
85+
86+
-def _pip_self_version_check(session: "PipSession", options: Values) -> None:
87+
- from pip._internal.self_outdated_check import pip_self_version_check as check
88+
+def _pip_self_version_check_fetch(session: "PipSession", options: Values) -> Optional["UpgradePrompt"]:
89+
+ from pip._internal.self_outdated_check import pip_self_version_check_fetch
90+
+
91+
+ return pip_self_version_check_fetch(session, options)
92+
+
93+
94+
- check(session, options)
95+
+def _pip_self_version_check_emit(upgrade_prompt: Optional["UpgradePrompt"]) -> None:
96+
+ from pip._internal.self_outdated_check import pip_self_version_check_emit
97+
+
98+
+ pip_self_version_check_emit(upgrade_prompt)
99+
100+
101+
class IndexGroupCommand(Command, SessionCommandMixin):
102+
@@ -144,7 +154,8 @@ class IndexGroupCommand(Command, SessionCommandMixin):
103+
This also corresponds to the commands that permit the pip version check.
104+
"""
105+
106+
- def handle_pip_version_check(self, options: Values) -> None:
107+
+ @contextlib.contextmanager
108+
+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]:
109+
"""
110+
Do the pip version check if not disabled.
111+
112+
@@ -154,17 +165,27 @@ class IndexGroupCommand(Command, SessionCommandMixin):
113+
assert hasattr(options, "no_index")
114+
115+
if options.disable_pip_version_check or options.no_index:
116+
+ yield
117+
return
118+
119+
+ upgrade_prompt: Optional["UpgradePrompt"] = None
120+
try:
121+
- # Otherwise, check if we're using the latest version of pip available.
122+
session = self._build_session(
123+
options,
124+
retries=0,
125+
timeout=min(5, options.timeout),
126+
)
127+
with session:
128+
- _pip_self_version_check(session, options)
129+
+ upgrade_prompt = _pip_self_version_check_fetch(session, options)
130+
except Exception:
131+
logger.warning("There was an error checking the latest version of pip.")
132+
logger.debug("See below for error", exc_info=True)
133+
+
134+
+ try:
135+
+ yield
136+
+ finally:
137+
+ try:
138+
+ _pip_self_version_check_emit(upgrade_prompt)
139+
+ except Exception:
140+
+ logger.warning("There was an error checking the latest version of pip.")
141+
+ logger.debug("See below for error", exc_info=True)
142+
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
143+
index ad45a2f..298b92c 100644
144+
--- a/src/pip/_internal/commands/install.py
145+
+++ b/src/pip/_internal/commands/install.py
146+
@@ -1,4 +1,10 @@
147+
import errno
148+
+from __future__ import annotations
149+
+
150+
+import contextlib
151+
+from collections.abc import Iterator
152+
+from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
153+
+
154+
import json
155+
import operator
156+
import os
157+
diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py
158+
index 82fc46a..ba00f82 100644
159+
--- a/src/pip/_internal/commands/list.py
160+
+++ b/src/pip/_internal/commands/list.py
161+
@@ -1,7 +1,8 @@
162+
import json
163+
import logging
164+
+import contextlib
165+
from optparse import Values
166+
-from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
167+
+from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast, Iterator
168+
169+
from pip._vendor.packaging.utils import canonicalize_name
170+
from pip._vendor.packaging.version import Version
171+
@@ -134,9 +135,13 @@ class ListCommand(IndexGroupCommand):
172+
self.parser.insert_option_group(0, index_opts)
173+
self.parser.insert_option_group(0, self.cmd_opts)
174+
175+
- def handle_pip_version_check(self, options: Values) -> None:
176+
- if options.outdated or options.uptodate:
177+
- super().handle_pip_version_check(options)
178+
+ @contextlib.contextmanager
179+
+ def pip_version_check(self, options: Values, args: List[str]) -> Iterator[None]:
180+
+ if not (options.outdated or options.uptodate):
181+
+ yield
182+
+ return
183+
+ with super().pip_version_check(options, args):
184+
+ yield
185+
186+
def _build_package_finder(
187+
self, options: Values, session: "PipSession"
188+
diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py
189+
index f9a91af..fe0e0d2 100644
190+
--- a/src/pip/_internal/self_outdated_check.py
191+
+++ b/src/pip/_internal/self_outdated_check.py
192+
@@ -1,5 +1,4 @@
193+
import datetime
194+
-import functools
195+
import hashlib
196+
import json
197+
import logging
198+
@@ -7,7 +6,7 @@ import optparse
199+
import os.path
200+
import sys
201+
from dataclasses import dataclass
202+
-from typing import Any, Callable, Dict, Optional
203+
+from typing import Any, Dict, Optional
204+
205+
from pip._vendor.packaging.version import Version
206+
from pip._vendor.packaging.version import parse as parse_version
207+
@@ -26,7 +25,8 @@ from pip._internal.utils.entrypoints import (
208+
get_best_invocation_for_this_python,
209+
)
210+
from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace
211+
-from pip._internal.utils.misc import ensure_dir
212+
+from pip._internal.utils.misc import ensure_dir, check_externally_managed
213+
+from pip._internal.exceptions import ExternallyManagedEnvironment
214+
215+
_WEEK = datetime.timedelta(days=7)
216+
217+
@@ -149,14 +149,6 @@ class UpgradePrompt:
218+
)
219+
220+
221+
-def was_installed_by_pip(pkg: str) -> bool:
222+
- """Checks whether pkg was installed by pip
223+
-
224+
- This is used not to display the upgrade message when pip is in fact
225+
- installed by system package manager, such as dnf on Fedora.
226+
- """
227+
- dist = get_default_environment().get_distribution(pkg)
228+
- return dist is not None and "pip" == dist.installer
229+
230+
231+
def _get_current_remote_pip_version(
232+
@@ -187,28 +179,15 @@ def _get_current_remote_pip_version(
233+
return str(best_candidate.version)
234+
235+
236+
-def _self_version_check_logic(
237+
- *,
238+
- state: SelfCheckState,
239+
- current_time: datetime.datetime,
240+
- local_version: Version,
241+
- get_remote_version: Callable[[], Optional[str]],
242+
+def _compute_upgrade_prompt(
243+
+ local_version: Version, remote_version_str: str, installed_by_pip: bool
244+
) -> Optional[UpgradePrompt]:
245+
- remote_version_str = state.get(current_time)
246+
- if remote_version_str is None:
247+
- remote_version_str = get_remote_version()
248+
- if remote_version_str is None:
249+
- logger.debug("No remote pip version found")
250+
- return None
251+
- state.set(remote_version_str, current_time)
252+
-
253+
remote_version = parse_version(remote_version_str)
254+
logger.debug("Remote version of pip: %s", remote_version)
255+
logger.debug("Local version of pip: %s", local_version)
256+
+ logger.debug("Was pip installed by pip? %s", installed_by_pip)
257+
258+
- pip_installed_by_pip = was_installed_by_pip("pip")
259+
- logger.debug("Was pip installed by pip? %s", pip_installed_by_pip)
260+
- if not pip_installed_by_pip:
261+
+ if not installed_by_pip:
262+
return None # Only suggest upgrade if pip is installed by pip.
263+
264+
local_version_is_older = (
265+
@@ -221,24 +200,44 @@ def _self_version_check_logic(
266+
return None
267+
268+
269+
-def pip_self_version_check(session: PipSession, options: optparse.Values) -> None:
270+
- """Check for an update for pip.
271+
+def pip_self_version_check_fetch(
272+
+ session: PipSession, options: optparse.Values
273+
+) -> Optional[UpgradePrompt]:
274+
+ """Compute the pip upgrade prompt, if any, before the command runs.
275+
276+
Limit the frequency of checks to once per week. State is stored either in
277+
the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix
278+
of the pip script path.
279+
+
280+
+ Pair with :func:`pip_self_version_check_emit`, which displays the prompt
281+
+ after the command body runs.
282+
"""
283+
installed_dist = get_default_environment().get_distribution("pip")
284+
if not installed_dist:
285+
- return
286+
+ return None
287+
+ try:
288+
+ check_externally_managed()
289+
+ except ExternallyManagedEnvironment:
290+
+ return None
291+
292+
- upgrade_prompt = _self_version_check_logic(
293+
- state=SelfCheckState(cache_dir=options.cache_dir),
294+
- current_time=datetime.datetime.now(datetime.timezone.utc),
295+
+ state = SelfCheckState(cache_dir=options.cache_dir)
296+
+ current_time = datetime.datetime.now(datetime.timezone.utc)
297+
+ remote_version_str = state.get(current_time)
298+
+ if remote_version_str is None:
299+
+ remote_version_str = _get_current_remote_pip_version(session, options)
300+
+ if remote_version_str is None:
301+
+ logger.debug("No remote pip version found")
302+
+ return None
303+
+ state.set(remote_version_str, current_time)
304+
+
305+
+ return _compute_upgrade_prompt(
306+
local_version=installed_dist.version,
307+
- get_remote_version=functools.partial(
308+
- _get_current_remote_pip_version, session, options
309+
- ),
310+
+ remote_version_str=remote_version_str,
311+
+ installed_by_pip=installed_dist.installer == "pip",
312+
)
313+
+
314+
+
315+
+def pip_self_version_check_emit(upgrade_prompt: Optional[UpgradePrompt]) -> None:
316+
+ """Emit the upgrade prompt captured by :func:`pip_self_version_check_fetch`."""
317+
if upgrade_prompt is not None:
318+
logger.warning("%s", upgrade_prompt, extra={"rich": True})
319+
diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py
320+
index f9fae65..5fdf94a 100644
321+
--- a/tests/unit/test_base_command.py
322+
+++ b/tests/unit/test_base_command.py
323+
@@ -97,14 +97,14 @@ class TestCommand:
324+
assert "Traceback (most recent call last):" in stderr
325+
326+
327+
-@patch("pip._internal.cli.index_command.Command.handle_pip_version_check")
328+
-def test_handle_pip_version_check_called(mock_handle_version_check: Mock) -> None:
329+
+@patch("pip._internal.cli.index_command.Command.pip_version_check")
330+
+def test_pip_version_check_called(mock_version_check: Mock) -> None:
331+
"""
332+
- Check that Command.handle_pip_version_check() is called.
333+
+ Check that ``Command.pip_version_check()`` wraps the command body.
334+
"""
335+
cmd = FakeCommand()
336+
cmd.main([])
337+
- mock_handle_version_check.assert_called_once()
338+
+ mock_version_check.assert_called_once()
339+
340+
341+
def test_log_command_success(fixed_time: None, tmpdir: Path) -> None:
342+
diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py
343+
index 9d5aefe..7e944ce 100644
344+
--- a/tests/unit/test_commands.py
345+
+++ b/tests/unit/test_commands.py
346+
@@ -87,8 +87,8 @@ def test_index_group_commands() -> None:
347+
(True, True, False),
348+
],
349+
)
350+
-@mock.patch("pip._internal.cli.index_command._pip_self_version_check")
351+
-def test_index_group_handle_pip_version_check(
352+
+@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch")
353+
+def test_index_group_pip_version_check(
354+
mock_version_check: mock.Mock,
355+
command_name: str,
356+
disable_pip_version_check: bool,
357+
@@ -96,9 +96,8 @@ def test_index_group_handle_pip_version_check(
358+
expected_called: bool,
359+
) -> None:
360+
"""
361+
- Test whether pip_self_version_check() is called when
362+
- handle_pip_version_check() is called, for each of the
363+
- IndexGroupCommand classes.
364+
+ Test whether self-version check is performed when ``pip_version_check()``
365+
+ is called, for each of the IndexGroupCommand classes.
366+
"""
367+
command = create_command(command_name)
368+
options = command.parser.get_default_values()
369+
--
370+
2.45.4
371+

SPECS/python-pip/python-pip.spec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A tool for installing and managing Python packages}
55
Summary: A tool for installing and managing Python packages
66
Name: python-pip
77
Version: 24.2
8-
Release: 7%{?dist}
8+
Release: 8%{?dist}
99
License: MIT AND Python-2.0.1 AND Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND ISC AND LGPL-2.1-only AND MPL-2.0 AND (Apache-2.0 OR BSD-2-Clause)
1010
Vendor: Microsoft Corporation
1111
Distribution: Azure Linux
@@ -17,6 +17,7 @@ Patch1: CVE-2025-8869.patch
1717
Patch2: CVE-2025-50181.patch
1818
Patch3: CVE-2026-1703.patch
1919
Patch4: CVE-2026-3219.patch
20+
Patch5: CVE-2026-6357.patch
2021

2122
BuildArch: noarch
2223

@@ -60,6 +61,9 @@ BuildRequires: python3-wheel
6061
%{python3_sitelib}/pip*
6162

6263
%changelog
64+
* Fri May 08 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 24.2-8
65+
- Patch for CVE-2026-6357
66+
6367
* Wed Apr 22 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 24.2-7
6468
- Patch for CVE-2026-3219
6569

0 commit comments

Comments
 (0)