From 8cbc5ac07b43b0fd8f5586f21b4bb04eaeb6554f Mon Sep 17 00:00:00 2001 From: Jon Slobodzian Date: Fri, 23 Oct 2026 23:41:53 -0400 Subject: [PATCH 1/4] [python-virtualenv] Distrust affected eMudhra and AAA certificate roots Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e62137f2-2602-418d-ba0d-3b1f2ff0259e --- .../python-virtualenv/python-virtualenv.spec | 11 +- .../remove-compromised-wheel-ca-roots.patch | 119 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 SPECS/python-virtualenv/remove-compromised-wheel-ca-roots.patch diff --git a/SPECS/python-virtualenv/python-virtualenv.spec b/SPECS/python-virtualenv/python-virtualenv.spec index 30a9f798ce4..5c8204bca72 100644 --- a/SPECS/python-virtualenv/python-virtualenv.spec +++ b/SPECS/python-virtualenv/python-virtualenv.spec @@ -1,7 +1,7 @@ Summary: Virtual Python Environment builder Name: python-virtualenv Version: 20.36.1 -Release: 7%{?dist} +Release: 8%{?dist} License: MIT Vendor: Microsoft Corporation Distribution: Azure Linux @@ -24,6 +24,7 @@ Patch1011: CVE-2026-13346v0.patch Patch1012: CVE-2026-13346v1.patch Patch1013: CVE-2026-102925.patch Patch1014: CVE-2026-102930.patch +Patch1015: remove-compromised-wheel-ca-roots.patch BuildArch: noarch %description @@ -58,6 +59,7 @@ virtualenv is a tool to create isolated Python environment. %patch -P 0 -p1 %patch -P 1013 -p1 %patch -P 1014 -p1 +%patch -P 1015 -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. @@ -158,6 +160,9 @@ zip -r ../src/virtualenv/seed/wheels/embed/unpacked_wheel-0.45.1-py3-none-any.wh popd rm -rf unpacked_wheel-0.45.1-py3-none-any +%{__python3} Tools/remove-untrusted-wheel-roots.py \ + --expected-removals 3 src/virtualenv/seed/wheels/embed/pip-*.whl + %generate_buildrequires %build @@ -179,6 +184,10 @@ tox -e py %{_bindir}/virtualenv %changelog +* Fri Oct 23 2026 Jon Slobodzian - 20.36.1-8 +- Remove affected eMudhra and AAA roots from both embedded pip wheels. +- Regenerate wheel RECORD entries after the existing security backports. + * Mon Oct 05 2026 Kshitiz Godara - 20.36.1-7 - Patch for CVE-2026-102925 and CVE-2026-102930 diff --git a/SPECS/python-virtualenv/remove-compromised-wheel-ca-roots.patch b/SPECS/python-virtualenv/remove-compromised-wheel-ca-roots.patch new file mode 100644 index 00000000000..3eeaa9c7d77 --- /dev/null +++ b/SPECS/python-virtualenv/remove-compromised-wheel-ca-roots.patch @@ -0,0 +1,119 @@ +Subject: [PATCH] Distrust incident-related eMudhra and AAA trust anchors + +Remove only the affected trust anchors, including the AAA cross-signing +path. Preserve the unaffected emSign G1, C1 and C3 anchors. +Reference: https://emudhra.we-have-a-problem.com/ +Upstream-status: Azure Linux trust-store backport + +--- /dev/null ++++ b/Tools/remove-untrusted-wheel-roots.py +@@ -0,0 +1,109 @@ ++"""Remove incident-related trust anchors from bundled wheels and renew RECORD.""" ++ ++import argparse ++import base64 ++import csv ++import hashlib ++import io ++from pathlib import Path ++import re ++import ssl ++import tempfile ++import zipfile ++ ++ ++UNTRUSTED_SHA256 = { ++ "d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4", ++ "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b", ++} ++CERTIFICATE = re.compile( ++ rb"-----BEGIN CERTIFICATE-----\s+[A-Za-z0-9+/=\s]+?-----END CERTIFICATE-----" ++) ++ ++ ++def prepare_wheel(path): ++ with zipfile.ZipFile(path) as archive: ++ infos = archive.infolist() ++ if len({info.filename for info in infos}) != len(infos): ++ raise ValueError(f"{path}: duplicate wheel members") ++ records = [ ++ info.filename ++ for info in infos ++ if info.filename.endswith(".dist-info/RECORD") ++ ] ++ if len(records) != 1: ++ raise ValueError(f"{path}: expected exactly one wheel RECORD") ++ record = records[0] ++ if any( ++ name in archive.namelist() for name in (record + ".jws", record + ".p7s") ++ ): ++ raise ValueError(f"{path}: cannot rewrite a signed wheel") ++ payload = {info.filename: archive.read(info) for info in infos} ++ ++ removed = 0 ++ ++ def retain_certificate(match): ++ nonlocal removed ++ der = ssl.PEM_cert_to_DER_cert(match.group().decode("ascii")) ++ if hashlib.sha256(der).hexdigest() in UNTRUSTED_SHA256: ++ removed += 1 ++ return b"" ++ return match.group() ++ ++ for name, data in payload.items(): ++ if name.endswith((".pem", ".crt")): ++ payload[name] = CERTIFICATE.sub(retain_certificate, data) ++ ++ if not removed: ++ return None, 0 ++ ++ output = io.StringIO(newline="") ++ writer = csv.writer(output, lineterminator="\n") ++ for info in infos: ++ name = info.filename ++ if info.is_dir(): ++ continue ++ if name == record: ++ writer.writerow((name, "", "")) ++ else: ++ data = payload[name] ++ digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest()) ++ writer.writerow((name, "sha256=" + digest.rstrip(b"=").decode(), len(data))) ++ payload[record] = output.getvalue().encode("utf-8") ++ return (path, infos, payload), removed ++ ++ ++def main(): ++ parser = argparse.ArgumentParser(description=__doc__) ++ parser.add_argument("--expected-removals", type=int, required=True) ++ parser.add_argument("wheels", nargs="+", type=Path) ++ args = parser.parse_args() ++ plans = [] ++ total = 0 ++ for path in args.wheels: ++ plan, removed = prepare_wheel(path) ++ if plan is not None: ++ plans.append(plan) ++ total += removed ++ print(f"{path}: removing {removed} affected trust anchors") ++ if total != args.expected_removals: ++ raise ValueError( ++ f"Expected {args.expected_removals} trust anchors, found {total}; " ++ "review the bundled wheels before changing this expectation" ++ ) ++ for path, infos, payload in plans: ++ with tempfile.NamedTemporaryFile( ++ dir=path.parent, prefix=path.name + ".", suffix=".tmp", delete=False ++ ) as temporary: ++ temporary_path = Path(temporary.name) ++ try: ++ with zipfile.ZipFile(temporary_path, "w") as archive: ++ for info in infos: ++ archive.writestr(info, payload[info.filename]) ++ temporary_path.replace(path) ++ finally: ++ temporary_path.unlink(missing_ok=True) ++ ++ ++if __name__ == "__main__": ++ main() From aa96987ee9826574394ee2443efde04a91c4906f Mon Sep 17 00:00:00 2001 From: Jon Slobodzian Date: Sat, 24 Oct 2026 22:13:49 -0400 Subject: [PATCH 2/4] python-virtualenv: repair pinned tox 4 package checks Use private test environments and constrained dependencies from configured pip sources instead of replacing RPM-owned packages or requiring an offline source bundle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e62137f2-2602-418d-ba0d-3b1f2ff0259e --- .../coverage-subprocess-plugin.patch | 17 +++++++++++ .../python-virtualenv/python-virtualenv.spec | 20 ++++++++++--- SPECS/python-virtualenv/test-constraints.txt | 28 +++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 SPECS/python-virtualenv/coverage-subprocess-plugin.patch create mode 100644 SPECS/python-virtualenv/test-constraints.txt diff --git a/SPECS/python-virtualenv/coverage-subprocess-plugin.patch b/SPECS/python-virtualenv/coverage-subprocess-plugin.patch new file mode 100644 index 00000000000..c31a95f440c --- /dev/null +++ b/SPECS/python-virtualenv/coverage-subprocess-plugin.patch @@ -0,0 +1,17 @@ +From: Azure Linux package maintainers +Subject: [PATCH] Propagate the configured coverage plugin to child interpreters + +Subprocess coverage uses the same configuration as the parent. Supply +its covdefaults plugin alongside the copied coverage package. + +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -252,7 +252,7 @@ + class EnableCoverage: + _COV_FILE: ClassVar[Path] = Path(coverage.__file__) + _ROOT_COV_FILES_AND_FOLDERS: ClassVar[list[Path]] = [ +- i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith("coverage") ++ i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith(("coverage", "covdefaults")) + ] + + def __init__(self, link) -> None: diff --git a/SPECS/python-virtualenv/python-virtualenv.spec b/SPECS/python-virtualenv/python-virtualenv.spec index 5c8204bca72..5a04d2ab49d 100644 --- a/SPECS/python-virtualenv/python-virtualenv.spec +++ b/SPECS/python-virtualenv/python-virtualenv.spec @@ -8,6 +8,7 @@ Distribution: Azure Linux Group: Development/Languages/Python URL: https://pypi.python.org/pypi/virtualenv Source0: https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz +Source1: test-constraints.txt Patch0: 0001-replace-to-flit.patch Patch1000: CVE-2025-50181.patch Patch1001: CVE-2026-1703v0.patch @@ -25,6 +26,7 @@ Patch1012: CVE-2026-13346v1.patch Patch1013: CVE-2026-102925.patch Patch1014: CVE-2026-102930.patch Patch1015: remove-compromised-wheel-ca-roots.patch +Patch1016: coverage-subprocess-plugin.patch BuildArch: noarch %description @@ -60,6 +62,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. @@ -172,10 +175,18 @@ rm -rf unpacked_wheel-0.45.1-py3-none-any %pyproject_install %check -pip3 install 'tox>=3.27.1,<4.0.0' -# skip "test_can_build_c_extensions" tests since they fail on python3_version >= 3.12. See https://src.fedoraproject.org/rpms/python-virtualenv/blob/rawhide/f/python-virtualenv.spec#_153 -sed -i 's/coverage run -m pytest {posargs:--junitxml {toxworkdir}\/junit\.{envname}\.xml tests --int}/coverage run -m pytest {posargs:--junitxml {toxworkdir}\/junit\.{envname}\.xml tests -k "not test_can_build_c_extensions" --int}/g' tox.ini -tox -e py +# Keep pip installs private, and keep the bootstrap outside zipapp's source copy. +check_env=$(mktemp -d "$PWD/../virtualenv-check.XXXXXX") +python3 -m venv "$check_env" +"$check_env/bin/python" -m pip install --disable-pip-version-check \ + --constraint %{SOURCE1} 'tox==4.28.0' +# tox 4.28 only applies overrides to sections that already exist. +printf '\n[testenv:.pkg]\npass_env = PIP_*\nconstraints = %{SOURCE1}\n' >> tox.ini +"$check_env/bin/python" -m tox \ + -x 'testenv.pass_env+=PIP_*' \ + -x 'testenv.constraints=%{SOURCE1}' \ + -e py -- --junitxml "$PWD/.tox/junit.py.xml" \ + tests -k 'not test_can_build_c_extensions' --int %files -n python3-virtualenv %defattr(-,root,root,-) @@ -187,6 +198,7 @@ tox -e py * Fri Oct 23 2026 Jon Slobodzian - 20.36.1-8 - Remove affected eMudhra and AAA roots from both embedded pip wheels. - Regenerate wheel RECORD entries after the existing security backports. +- Run pinned tox 4 and test dependencies in private environments using configured pip sources. * Mon Oct 05 2026 Kshitiz Godara - 20.36.1-7 - Patch for CVE-2026-102925 and CVE-2026-102930 diff --git a/SPECS/python-virtualenv/test-constraints.txt b/SPECS/python-virtualenv/test-constraints.txt new file mode 100644 index 00000000000..386e580616f --- /dev/null +++ b/SPECS/python-virtualenv/test-constraints.txt @@ -0,0 +1,28 @@ +cachetools==6.1.0 +chardet==5.2.0 +colorama==0.4.6 +covdefaults==2.3.0 +coverage==7.10.6 +coverage-enable-subprocess==1.0 +distlib==0.3.8 +filelock==3.20.3 +flaky==3.8.1 +flit-core==3.12.0 +iniconfig==2.1.0 +packaging==25.0 +platformdirs==4.3.8 +pluggy==1.6.0 +pygments==2.19.2 +pyproject-api==1.9.1 +pytest==8.4.2 +pytest-env==1.1.5 +pytest-mock==3.15.1 +pytest-randomly==3.16.0 +pytest-timeout==2.4.0 +python-dateutil==2.9.0.post0 +setuptools==80.9.0 +six==1.17.0 +time-machine==2.19.0 +tox==4.28.0 +typing-extensions==4.13.2 +virtualenv==20.36.1 From 042d455a7caecc55a80b973db2a20357625a0f93 Mon Sep 17 00:00:00 2001 From: Jon Slobodzian Date: Sat, 24 Oct 2026 22:38:49 -0400 Subject: [PATCH 3/4] python-virtualenv: run package checks without test patches Use the built wheel and pinned test dependencies in a private environment. Run pytest directly without enabling subprocess coverage, retaining the existing test selection and all certificate/security fixes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e62137f2-2602-418d-ba0d-3b1f2ff0259e --- .../coverage-subprocess-plugin.patch | 17 ---------------- .../python-virtualenv/python-virtualenv.spec | 20 +++++++++---------- SPECS/python-virtualenv/test-constraints.txt | 7 ------- 3 files changed, 9 insertions(+), 35 deletions(-) delete mode 100644 SPECS/python-virtualenv/coverage-subprocess-plugin.patch diff --git a/SPECS/python-virtualenv/coverage-subprocess-plugin.patch b/SPECS/python-virtualenv/coverage-subprocess-plugin.patch deleted file mode 100644 index c31a95f440c..00000000000 --- a/SPECS/python-virtualenv/coverage-subprocess-plugin.patch +++ /dev/null @@ -1,17 +0,0 @@ -From: Azure Linux package maintainers -Subject: [PATCH] Propagate the configured coverage plugin to child interpreters - -Subprocess coverage uses the same configuration as the parent. Supply -its covdefaults plugin alongside the copied coverage package. - ---- a/tests/conftest.py -+++ b/tests/conftest.py -@@ -252,7 +252,7 @@ - class EnableCoverage: - _COV_FILE: ClassVar[Path] = Path(coverage.__file__) - _ROOT_COV_FILES_AND_FOLDERS: ClassVar[list[Path]] = [ -- i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith("coverage") -+ i for i in _COV_FILE.parents[1].iterdir() if i.name.startswith(("coverage", "covdefaults")) - ] - - def __init__(self, link) -> None: diff --git a/SPECS/python-virtualenv/python-virtualenv.spec b/SPECS/python-virtualenv/python-virtualenv.spec index 5a04d2ab49d..5430def0482 100644 --- a/SPECS/python-virtualenv/python-virtualenv.spec +++ b/SPECS/python-virtualenv/python-virtualenv.spec @@ -26,7 +26,6 @@ Patch1012: CVE-2026-13346v1.patch Patch1013: CVE-2026-102925.patch Patch1014: CVE-2026-102930.patch Patch1015: remove-compromised-wheel-ca-roots.patch -Patch1016: coverage-subprocess-plugin.patch BuildArch: noarch %description @@ -62,7 +61,6 @@ 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. @@ -175,17 +173,17 @@ rm -rf unpacked_wheel-0.45.1-py3-none-any %pyproject_install %check -# Keep pip installs private, and keep the bootstrap outside zipapp's source copy. +# Keep the test environment outside zipapp's source copy. check_env=$(mktemp -d "$PWD/../virtualenv-check.XXXXXX") python3 -m venv "$check_env" +set -- %{_pyproject_wheeldir}/*.whl +test "$#" -eq 1 "$check_env/bin/python" -m pip install --disable-pip-version-check \ - --constraint %{SOURCE1} 'tox==4.28.0' -# tox 4.28 only applies overrides to sections that already exist. -printf '\n[testenv:.pkg]\npass_env = PIP_*\nconstraints = %{SOURCE1}\n' >> tox.ini -"$check_env/bin/python" -m tox \ - -x 'testenv.pass_env+=PIP_*' \ - -x 'testenv.constraints=%{SOURCE1}' \ - -e py -- --junitxml "$PWD/.tox/junit.py.xml" \ + --constraint %{SOURCE1} "$1[test]" +export PATH="$check_env/bin:$PATH" +unset COVERAGE_PROCESS_START +export PYTHONWARNDEFAULTENCODING=1 +"$check_env/bin/python" -m pytest --junitxml junit.py.xml \ tests -k 'not test_can_build_c_extensions' --int %files -n python3-virtualenv @@ -198,7 +196,7 @@ printf '\n[testenv:.pkg]\npass_env = PIP_*\nconstraints = %{SOURCE1}\n' >> tox.i * Fri Oct 23 2026 Jon Slobodzian - 20.36.1-8 - Remove affected eMudhra and AAA roots from both embedded pip wheels. - Regenerate wheel RECORD entries after the existing security backports. -- Run pinned tox 4 and test dependencies in private environments using configured pip sources. +- Run pytest directly with pinned test dependencies in a private environment. * Mon Oct 05 2026 Kshitiz Godara - 20.36.1-7 - Patch for CVE-2026-102925 and CVE-2026-102930 diff --git a/SPECS/python-virtualenv/test-constraints.txt b/SPECS/python-virtualenv/test-constraints.txt index 386e580616f..e290f5942f4 100644 --- a/SPECS/python-virtualenv/test-constraints.txt +++ b/SPECS/python-virtualenv/test-constraints.txt @@ -1,19 +1,14 @@ -cachetools==6.1.0 -chardet==5.2.0 -colorama==0.4.6 covdefaults==2.3.0 coverage==7.10.6 coverage-enable-subprocess==1.0 distlib==0.3.8 filelock==3.20.3 flaky==3.8.1 -flit-core==3.12.0 iniconfig==2.1.0 packaging==25.0 platformdirs==4.3.8 pluggy==1.6.0 pygments==2.19.2 -pyproject-api==1.9.1 pytest==8.4.2 pytest-env==1.1.5 pytest-mock==3.15.1 @@ -23,6 +18,4 @@ python-dateutil==2.9.0.post0 setuptools==80.9.0 six==1.17.0 time-machine==2.19.0 -tox==4.28.0 typing-extensions==4.13.2 -virtualenv==20.36.1 From c267d3ffc090f7955e6ca4bee1b4cd8da0839fd7 Mon Sep 17 00:00:00 2001 From: Jon Slobodzian Date: Sat, 24 Oct 2026 22:49:37 -0400 Subject: [PATCH 4/4] python-virtualenv: inline test constraints in package checks Keep the same 21 dependency pins in a quoted heredoc inside %check. Remove the separate constraints source without changing test selection or certificate/security fixes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e62137f2-2602-418d-ba0d-3b1f2ff0259e --- .../python-virtualenv/python-virtualenv.spec | 26 +++++++++++++++++-- SPECS/python-virtualenv/test-constraints.txt | 21 --------------- 2 files changed, 24 insertions(+), 23 deletions(-) delete mode 100644 SPECS/python-virtualenv/test-constraints.txt diff --git a/SPECS/python-virtualenv/python-virtualenv.spec b/SPECS/python-virtualenv/python-virtualenv.spec index 5430def0482..4548a3163c6 100644 --- a/SPECS/python-virtualenv/python-virtualenv.spec +++ b/SPECS/python-virtualenv/python-virtualenv.spec @@ -8,7 +8,6 @@ Distribution: Azure Linux Group: Development/Languages/Python URL: https://pypi.python.org/pypi/virtualenv Source0: https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz -Source1: test-constraints.txt Patch0: 0001-replace-to-flit.patch Patch1000: CVE-2025-50181.patch Patch1001: CVE-2026-1703v0.patch @@ -176,10 +175,33 @@ rm -rf unpacked_wheel-0.45.1-py3-none-any # Keep the test environment outside zipapp's source copy. check_env=$(mktemp -d "$PWD/../virtualenv-check.XXXXXX") python3 -m venv "$check_env" +cat > "$check_env/constraints.txt" <<'EOF' +covdefaults==2.3.0 +coverage==7.10.6 +coverage-enable-subprocess==1.0 +distlib==0.3.8 +filelock==3.20.3 +flaky==3.8.1 +iniconfig==2.1.0 +packaging==25.0 +platformdirs==4.3.8 +pluggy==1.6.0 +pygments==2.19.2 +pytest==8.4.2 +pytest-env==1.1.5 +pytest-mock==3.15.1 +pytest-randomly==3.16.0 +pytest-timeout==2.4.0 +python-dateutil==2.9.0.post0 +setuptools==80.9.0 +six==1.17.0 +time-machine==2.19.0 +typing-extensions==4.13.2 +EOF set -- %{_pyproject_wheeldir}/*.whl test "$#" -eq 1 "$check_env/bin/python" -m pip install --disable-pip-version-check \ - --constraint %{SOURCE1} "$1[test]" + --constraint "$check_env/constraints.txt" "$1[test]" export PATH="$check_env/bin:$PATH" unset COVERAGE_PROCESS_START export PYTHONWARNDEFAULTENCODING=1 diff --git a/SPECS/python-virtualenv/test-constraints.txt b/SPECS/python-virtualenv/test-constraints.txt deleted file mode 100644 index e290f5942f4..00000000000 --- a/SPECS/python-virtualenv/test-constraints.txt +++ /dev/null @@ -1,21 +0,0 @@ -covdefaults==2.3.0 -coverage==7.10.6 -coverage-enable-subprocess==1.0 -distlib==0.3.8 -filelock==3.20.3 -flaky==3.8.1 -iniconfig==2.1.0 -packaging==25.0 -platformdirs==4.3.8 -pluggy==1.6.0 -pygments==2.19.2 -pytest==8.4.2 -pytest-env==1.1.5 -pytest-mock==3.15.1 -pytest-randomly==3.16.0 -pytest-timeout==2.4.0 -python-dateutil==2.9.0.post0 -setuptools==80.9.0 -six==1.17.0 -time-machine==2.19.0 -typing-extensions==4.13.2