python3-virtualenv: fix CVE-2026-113713

This patch applies the upstream fix as referenced in [2], using the
commit shown in [1]. Always copy seed packages from temporary app-data,
including when environment or configuration values enable symlinks.
This keeps the environment usable after the temporary cache is removed.

Although NVD lists releases from 21.14.8, the project advisory [3]
traces the incomplete guard to 20.0.6. The same unguarded constructor
is present in 21.2.1, where the upstream regression tests reproduce
dangling pip links after temporary-cache cleanup.

[1] https://github.com/pypa/virtualenv/commit/b81995c3feab9e1edb453c75ffc6616e1a77335e
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-113713
[3] https://github.com/pypa/virtualenv/security/advisories/GHSA-4f8h-vr72-p32c

Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Hetvi Thakar authored and Anuj Mittal committed 2026-11-09 09:10:38 +05:30
1 parent 021ed63fde
commit be26025948
2 files changed
+114

No files matched your search

@@ -0,0 +1,113 @@
From b81995c3feab9e1edb453c75ffc6616e1a77335e Mon Sep 17 00:00:00 2001
From: Bernát Gábor <gaborjbernat@gmail.com>
Date: Sat, 17 Oct 2026 10:09:55 -0700
Subject: [PATCH] fix(seed): copy from temporary app-data (#3437)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CVE: CVE-2026-113713
Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/b81995c3feab9e1edb453c75ffc6616e1a77335e]
Backport Changes:
- Omit the upstream release changelog fragment.
Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
---
.../seed/embed/via_app_data/via_app_data.py | 4 +-
.../embed/test_bootstrap_link_via_app_data.py | 52 ++++++++++++++++++-
2 files changed, 53 insertions(+), 3 deletions(-)
diff --git a/src/virtualenv/seed/embed/via_app_data/via_app_data.py b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
index 092ef74..44ba70d 100644
--- a/src/virtualenv/seed/embed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
@@ -37,7 +37,9 @@ LOGGER = logging.getLogger(__name__)
class FromAppData(BaseEmbed):
def __init__(self, options: VirtualEnvOptions) -> None:
super().__init__(options)
- self.symlinks = options.symlink_app_data
+ self.symlinks = options.symlink_app_data and not self.app_data.transient
+ if options.symlink_app_data and self.app_data.transient:
+ LOGGER.debug("copy seed packages because temporary app-data is removed after creation")
@classmethod
def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo, app_data: AppData) -> None:
diff --git a/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py b/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py
index a9917c4..fe07df7 100644
--- a/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py
+++ b/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py
@@ -3,9 +3,11 @@ from __future__ import annotations
import contextlib
import csv
import hashlib
+import logging
import os
import sys
from base64 import urlsafe_b64encode
+from pathlib import Path
from stat import S_IWGRP, S_IWOTH, S_IWUSR
from subprocess import Popen, check_call, run
from threading import Thread
@@ -21,11 +23,57 @@ from virtualenv.seed.wheels.embed import BUNDLE_FOLDER, BUNDLE_SUPPORT
from virtualenv.util.path import safe_delete
if TYPE_CHECKING:
- from pathlib import Path
-
from pytest_mock import MockerFixture
+@pytest.mark.parametrize(
+ "source",
+ [
+ pytest.param("environment", id="environment"),
+ pytest.param("configuration", id="configuration"),
+ pytest.param("command-line", id="command-line"),
+ ],
+)
+def test_transient_seed_survives_cache_cleanup(tmp_path: Path, source: str, unusable_cache: Path) -> None:
+ config: Final = tmp_path / "virtualenv.ini"
+ config.write_text("[virtualenv]\nsymlink_app_data = true\n" if source == "configuration" else "", encoding="utf-8")
+ env: Final = {
+ **os.environ,
+ "VIRTUALENV_CONFIG_FILE": str(config),
+ "VIRTUALENV_OVERRIDE_APP_DATA": str(unusable_cache),
+ }
+ if source == "environment":
+ env["VIRTUALENV_SYMLINK_APP_DATA"] = "true"
+ args: Final = [str(tmp_path / "env"), "--no-periodic-update", "--no-setuptools"]
+ if source == "command-line":
+ args.append("--symlink-app-data")
+ session: Final = cli_run(args, env=env)
+ assert (
+ session.creator.app_data.transient,
+ Path(str(session.creator.app_data)).exists(),
+ (session.creator.purelib / "pip" / "__init__.py").is_file(),
+ ) == (True, False, True)
+
+
+def test_transient_seed_reports_copy_fallback(
+ tmp_path: Path, caplog: pytest.LogCaptureFixture, unusable_cache: Path
+) -> None:
+ caplog.set_level(logging.DEBUG)
+ cli_run(
+ [str(tmp_path / "env"), "--no-periodic-update", "--no-setuptools"],
+ env={**os.environ, "VIRTUALENV_OVERRIDE_APP_DATA": str(unusable_cache), "VIRTUALENV_SYMLINK_APP_DATA": "true"},
+ setup_logging=False,
+ )
+ assert "copy seed packages because temporary app-data is removed after creation" in caplog.text
+
+
+@pytest.fixture
+def unusable_cache(tmp_path: Path) -> Path:
+ blocked: Final = tmp_path / "blocked"
+ blocked.touch()
+ return blocked / "cache"
+
+
@pytest.mark.usefixtures("_no_coverage")
@pytest.mark.parametrize(
("metadata_file", "content"),
--
2.35.6
@@ -30,6 +30,7 @@ SRC_URI += " \
file://CVE-2026-113716.patch \
file://CVE-2026-113722_p1.patch \
file://CVE-2026-113722_p2.patch \
file://CVE-2026-113713.patch \
"
BBCLASSEXTEND = "native nativesdk"