Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions SPECS/python-virtualenv/invalidate-pre-trust-update-seed-cache.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
Subject: [PATCH] Avoid pre-trust-update Azure Linux seed images

The embedded pip filenames are unchanged by the trust-store backport, so
the default app-data seeder can otherwise reuse images containing the old
certificate bundles. Select a new downstream image generation for newly
created environments without deleting caches used by existing environments.

Test both embedded pip versions with copy and symlink seeders. Preserve a
legacy cache containing distinguishable stale bundle bytes across the
upgrade, verify the corrected bundle is installed, and retain normal cache
reuse after the first corrected image is built. The test is offline.

Reference: https://github.com/microsoft/azurelinux/pull/19710
Upstream-status: Inappropriate [Azure Linux trust-store cache generation]

--- a/src/virtualenv/app_data/via_disk_folder.py
+++ b/src/virtualenv/app_data/via_disk_folder.py
@@ -104,7 +104,8 @@
return path

def wheel_image(self, for_py_version, name):
- return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
+ # Do not reuse seed images from before the October 2026 trust-store update.
+ return self.lock.path / "wheel" / for_py_version / "image" / "1-azl3-trust-202610" / name


class JSONStoreDisk(ContentStore, ABC):
--- /dev/null
+++ b/tests/unit/seed/embed/test_azurelinux_seed_cache_upgrade.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+from pathlib import Path
+from stat import S_IWUSR
+from zipfile import ZipFile
+
+import pytest
+
+from virtualenv.app_data import AppDataDiskFolder
+from virtualenv.discovery.py_info import PythonInfo
+from virtualenv.info import fs_supports_symlink
+from virtualenv.run import cli_run
+from virtualenv.seed.embed.via_app_data.pip_install.base import PipInstall
+from virtualenv.seed.wheels.embed import BUNDLE_FOLDER
+
+
+def legacy_wheel_image(self, for_py_version, name):
+ return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
+
+
+@pytest.mark.parametrize("pip_version", ["25.0.1", "25.3"])
+@pytest.mark.parametrize("symlink", [False, True] if fs_supports_symlink() else [False])
+def test_azurelinux_seed_cache_upgrade(tmp_path, monkeypatch, pip_version, symlink):
+ app_data = tmp_path / "app-data"
+ wheel = BUNDLE_FOLDER / f"pip-{pip_version}-py3-none-any.whl"
+ certificate_path = Path("pip/_vendor/certifi/cacert.pem")
+ with ZipFile(wheel) as archive:
+ corrected_bundle = archive.read(certificate_path.as_posix())
+ stale_bundle = corrected_bundle + b"\n# pre-update cached certificate bundle\n"
+ args = [
+ "--app-data",
+ str(app_data),
+ "--no-periodic-update",
+ "--no-download",
+ "--no-setuptools",
+ "--pip",
+ pip_version,
+ "--extra-search-dir",
+ str(BUNDLE_FOLDER),
+ ]
+ if symlink:
+ args.append("--symlink-app-data")
+
+ with monkeypatch.context() as legacy:
+ legacy.setattr(AppDataDiskFolder, "wheel_image", legacy_wheel_image)
+ cli_run([str(tmp_path / "populate"), *args])
+ installer = "SymlinkPipInstall" if symlink else "CopyPipInstall"
+ image = legacy_wheel_image(
+ AppDataDiskFolder(app_data),
+ PythonInfo.current_system().version_release_str,
+ Path(installer) / wheel.stem,
+ )
+ cached_bundle = image / certificate_path
+ mode = cached_bundle.stat().st_mode
+ cached_bundle.chmod(mode | S_IWUSR)
+ cached_bundle.write_bytes(stale_bundle)
+ cached_bundle.chmod(mode)
+ before = cli_run([str(tmp_path / "before-upgrade"), *args])
+ assert (before.creator.purelib / certificate_path).read_bytes() == stale_bundle
+
+ after = cli_run([str(tmp_path / "after-upgrade"), *args])
+ assert (after.creator.purelib / certificate_path).read_bytes() == corrected_bundle
+ assert cached_bundle.read_bytes() == stale_bundle
+ assert (before.creator.purelib / certificate_path).read_bytes() == stale_bundle
+
+ def unexpected_rebuild(self):
+ pytest.fail("the corrected seed image should be reused")
+
+ monkeypatch.setattr(PipInstall, "build_image", unexpected_rebuild)
+ repeated = cli_run([str(tmp_path / "repeat"), *args])
+ assert (repeated.creator.purelib / certificate_path).read_bytes() == corrected_bundle
10 changes: 9 additions & 1 deletion SPECS/python-virtualenv/python-virtualenv.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Summary: Virtual Python Environment builder
Name: python-virtualenv
Version: 20.36.1
Release: 8%{?dist}
Release: 9%{?dist}
License: MIT
Vendor: Microsoft Corporation
Distribution: Azure Linux
Expand All @@ -25,6 +25,7 @@ Patch1012: CVE-2026-13346v1.patch
Patch1013: CVE-2026-102925.patch
Patch1014: CVE-2026-102930.patch
Patch1015: remove-compromised-wheel-ca-roots.patch
Patch1016: invalidate-pre-trust-update-seed-cache.patch
BuildArch: noarch

%description
Expand Down Expand Up @@ -60,6 +61,7 @@ virtualenv is a tool to create isolated Python environment.
%patch -P 1013 -p1
%patch -P 1014 -p1
%patch -P 1015 -p1
%patch -P 1016 -p1

# Manual patching for CVE-2025-50181 and CVE-2026-1703v0
# For CVE-2025-50181, poolmanager.py file is located in 2 different places and each is of different version so the same patch cannot be applied to all of them.
Expand Down Expand Up @@ -215,6 +217,12 @@ export PYTHONWARNDEFAULTENCODING=1
%{_bindir}/virtualenv

%changelog
* Sun Oct 25 2026 Sean Dougherty <sdougherty@microsoft.com> - 20.36.1-9
- Rebuild seed images in a new cache namespace after the trust-store update.
- Test retained legacy caches with both pip wheels and copy/symlink seeders.
- Bypass legacy seed images for new environments without deleting files referenced by existing environments.
- Existing environments with affected pip CA bundles require a pip update or recreation.

* Fri Oct 23 2026 Jon Slobodzian <joslobo@microsoft.com> - 20.36.1-8
- Remove affected eMudhra and AAA roots from both embedded pip wheels.
- Regenerate wheel RECORD entries after the existing security backports.
Expand Down
Loading