mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-11-14 03:57:15 +00:00
python3-virtualenv: fix CVE-2026-113715
This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Reject line boundaries in stdlib venv paths and regenerate pyvenv.cfg from the selected interpreter and options, instead of merging potentially injected configuration read back from the intermediate file. [1] https://github.com/pypa/virtualenv/commit/b0008a58885ded9227faf8f910c6c402712c750a [2] https://github.com/pypa/virtualenv/security/advisories/GHSA-qmx5-4p3x-wvqf Signed-off-by: Hetvi Thakar <hthakar@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
1 parent
8d74b1295e
commit
2d3e0c1b5d
2 files changed
+166
No files matched your search
@@ -0,0 +1,165 @@
|
||||
From b0008a58885ded9227faf8f910c6c402712c750a Mon Sep 17 00:00:00 2001
|
||||
From: Bernát Gábor <gaborjbernat@gmail.com>
|
||||
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 <hthakar@cisco.com>
|
||||
---
|
||||
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
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user