diff --git a/meta-python/recipes-devtools/python/python3-virtualenv/CVE-2026-113715.patch b/meta-python/recipes-devtools/python/python3-virtualenv/CVE-2026-113715.patch new file mode 100644 index 0000000000..58b0c53439 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-virtualenv/CVE-2026-113715.patch @@ -0,0 +1,165 @@ +From b0008a58885ded9227faf8f910c6c402712c750a Mon Sep 17 00:00:00 2001 +From: Bernát Gábor +Date: Sat, 17 Oct 2026 02:56:02 -0700 +Subject: [PATCH] fix(create): reject ambiguous venv paths (#3426) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +CVE: CVE-2026-113715 +Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/b0008a58885ded9227faf8f910c6c402712c750a] + +Backport Changes: +- Reuse the identical _one_line helper already backported for + CVE-2026-102938 because 21.2.1 lacks the later + virtualenv.util.text.collapse_line_boundaries helper. +- Use the 21.2.1 system_executable API and GraalVM implementation name + in the launcher settings and regression test instead of the later + system_exe API and GraalPy name. +- Import Final in the older test module for the upstream annotations. +- Omit the upstream release changelog fragment. + +Signed-off-by: Hetvi Thakar +--- + src/virtualenv/create/via_global_ref/venv.py | 26 +++++++-- + tests/unit/create/test_creator.py | 56 ++++++++++++++++++++ + 2 files changed, 77 insertions(+), 5 deletions(-) + +diff --git a/src/virtualenv/create/via_global_ref/venv.py b/src/virtualenv/create/via_global_ref/venv.py +index 9c95d12..2bfd1b4 100644 +--- a/src/virtualenv/create/via_global_ref/venv.py ++++ b/src/virtualenv/create/via_global_ref/venv.py +@@ -1,11 +1,12 @@ + from __future__ import annotations + + import logging +-from copy import copy +-from typing import TYPE_CHECKING ++from argparse import ArgumentTypeError ++from typing import TYPE_CHECKING, Final + + from python_discovery import PythonInfo + ++from virtualenv.create.pyenv_cfg import _one_line as collapse_line_boundaries + from virtualenv.create.via_global_ref.store import handle_store_python + from virtualenv.util.error import ProcessCallFailedError + from virtualenv.util.path import ensure_dir +@@ -67,6 +68,20 @@ class Venv(ViaGlobalRefApi): + for exe in creator.executables(self.interpreter): + exe.run(creator, self.symlinks) + ++ @classmethod ++ def validate_dest(cls, raw_value: str) -> str: ++ return cls._check_line_boundaries(super().validate_dest(cls._check_line_boundaries(raw_value))) ++ ++ @staticmethod ++ def _check_line_boundaries(value: str) -> str: ++ # stdlib venv writes the destination into pyvenv.cfg without escaping line boundaries. ++ if collapse_line_boundaries(value) != value: ++ msg: Final = ( ++ f"destination {value!r} must not contain a line boundary as venv cannot record it in pyvenv.cfg" ++ ) ++ raise ArgumentTypeError(msg) ++ return value ++ + def create_inline(self) -> None: + from venv import EnvBuilder # noqa: PLC0415 + +@@ -95,10 +110,11 @@ class Venv(ViaGlobalRefApi): + return cmd # ty: ignore[invalid-return-type] + + def set_pyenv_cfg(self) -> None: +- # prefer venv options over ours, but keep our extra +- venv_content = copy(self.pyenv_cfg.refresh()) + super().set_pyenv_cfg() +- self.pyenv_cfg.update(venv_content) ++ if self.interpreter.implementation == "GraalVM": ++ # GraalPy launchers need these settings on Windows and macOS. ++ self.pyenv_cfg["venvlauncher_command"] = self.interpreter.system_executable ++ self.pyenv_cfg["base-executable"] = self.interpreter.system_executable + + def __getattribute__(self, item: str) -> object: + describe = object.__getattribute__(self, "describe") +diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py +index 3a8ad25..bd0f071 100644 +--- a/tests/unit/create/test_creator.py ++++ b/tests/unit/create/test_creator.py +@@ -19,6 +19,7 @@ from pathlib import Path + from stat import S_IREAD, S_IRGRP, S_IROTH + from textwrap import dedent + from threading import Thread ++from typing import Final + + import pytest + from python_discovery import PythonInfo +@@ -95,6 +96,42 @@ def system(session_app_data): + + + CURRENT_CREATORS = [i for i in CreatorSelector.for_interpreter(CURRENT).key_to_class if i != "builtin"] ++ ++ ++@pytest.mark.skipif("venv" not in CURRENT_CREATORS, reason="stdlib venv is unavailable") ++@pytest.mark.parametrize( ++ "boundary", ++ [ ++ pytest.param(char, id=name) ++ for name, char in ( ++ ("newline", "\n"), ++ ("return", "\r"), ++ ("vertical-tab", "\v"), ++ ("form-feed", "\f"), ++ ("file-separator", "\x1c"), ++ ("group-separator", "\x1d"), ++ ("record-separator", "\x1e"), ++ ("next-line", "\x85"), ++ ("line-separator", "\u2028"), ++ ("paragraph-separator", "\u2029"), ++ ) ++ ], ++) ++def test_venv_creator_rejects_line_boundary(tmp_path: Path, capsys: pytest.CaptureFixture[str], boundary: str) -> None: ++ destination: Final = tmp_path / f"project{boundary}name" ++ with pytest.raises(SystemExit) as context: ++ cli_run([str(destination), "--creator", "venv", "--no-seed"]) ++ assert context.value.code == 2 ++ assert "must not contain a line boundary" in capsys.readouterr().err ++ assert not list(tmp_path.iterdir()) ++ ++ ++@pytest.mark.skipif("venv" not in CURRENT_CREATORS, reason="stdlib venv is unavailable") ++def test_venv_creator_accepts_plain_destination(tmp_path: Path) -> None: ++ session: Final = cli_run([str(tmp_path / "environment"), "--creator", "venv", "--no-seed"]) ++ assert (session.creator.dest / "pyvenv.cfg").is_file() ++ ++ + CREATE_METHODS = [] + for k, v in CreatorSelector.for_interpreter(CURRENT).key_to_meta.items(): + if k in CURRENT_CREATORS: +@@ -270,6 +307,25 @@ def test_create_vcs_ignore_exists_override(tmp_path) -> None: + assert git_ignore.read_text(encoding="utf-8") == "magic" + + ++@pytest.mark.skipif(not CURRENT.has_venv, reason="requires venv") ++@pytest.mark.parametrize("global_packages", [pytest.param(False, id="isolated"), pytest.param(True, id="global")]) ++def test_venv_configuration_uses_selected_options(tmp_path: Path, global_packages: bool) -> None: ++ args: Final = [str(tmp_path / "env"), "--creator", "venv", "--no-seed"] ++ if global_packages: ++ args.append("--system-site-packages") ++ session: Final = cli_run(args) ++ session.creator.pyenv_cfg.path.write_text("vendor-marker = unused\n", encoding="utf-8") ++ session.creator.set_pyenv_cfg() ++ executable: Final = session.creator.interpreter.system_executable if CURRENT.implementation == "GraalVM" else None ++ expected: Final = { ++ "vendor-marker": None, ++ "include-system-site-packages": str(global_packages).lower(), ++ "venvlauncher_command": executable, ++ "base-executable": executable, ++ } ++ assert {key: session.creator.pyenv_cfg.content.get(key) for key in expected} == expected ++ ++ + @pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv") + def test_venv_fails_not_inline(tmp_path, capsys, mocker) -> None: + if hasattr(os, "geteuid") and os.geteuid() == 0: +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-virtualenv_21.2.1.bb b/meta-python/recipes-devtools/python/python3-virtualenv_21.2.1.bb index 2c584361ab..d514e79358 100644 --- a/meta-python/recipes-devtools/python/python3-virtualenv_21.2.1.bb +++ b/meta-python/recipes-devtools/python/python3-virtualenv_21.2.1.bb @@ -23,6 +23,7 @@ SRC_URI += " \ file://CVE-2026-113725.patch \ file://CVE-2026-113723.patch \ file://CVE-2026-115586.patch \ + file://CVE-2026-113715.patch \ " BBCLASSEXTEND = "native nativesdk"