mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-11-13 15:47:23 +00:00
python3-virtualenv: fix CVE-2026-113716
This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Replace linked output-directory components before creating interpreter, library and activation files. Reuse ensure_within from the preceding CVE-2026-113721 backport [3]. [1] https://github.com/pypa/virtualenv/commit/554d9f9d4f7758f5cf53f993031e29787f1f74c5 [2] https://nvd.nist.gov/vuln/detail/CVE-2026-113716 [3] https://github.com/pypa/virtualenv/commit/d25f28699eefb1fc34a251e5adf2b5de844c36cd 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
15567524b8
commit
d54f2315dd
2 files changed
+209
No files matched your search
@@ -0,0 +1,208 @@
|
||||
From 554d9f9d4f7758f5cf53f993031e29787f1f74c5 Mon Sep 17 00:00:00 2001
|
||||
From: Bernát Gábor <gaborjbernat@gmail.com>
|
||||
Date: Sat, 17 Oct 2026 03:36:11 -0700
|
||||
Subject: [PATCH] fix(create): replace linked output directories (#3429)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
CVE: CVE-2026-113716
|
||||
Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/554d9f9d4f7758f5cf53f993031e29787f1f74c5]
|
||||
|
||||
Backport Changes:
|
||||
- Add the fs_supports_symlink import required by the upstream utility
|
||||
tests; it is absent from the 21.2.1 test module.
|
||||
- Extend the older test_util path import with ensure_within; the later
|
||||
upstream module's safe_delete and symlink imports are unused here.
|
||||
- Omit the release-only changelog fragment.
|
||||
|
||||
Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
|
||||
---
|
||||
.../builtin/via_global_self_do.py | 4 +-
|
||||
src/virtualenv/create/via_global_ref/venv.py | 7 ++-
|
||||
tests/unit/create/test_creator.py | 36 +++++++++++++
|
||||
tests/unit/test_util.py | 54 ++++++++++++++++++-
|
||||
4 files changed, 94 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
|
||||
index f86861e..9ada4c1 100644
|
||||
--- a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
|
||||
+++ b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
|
||||
@@ -9,7 +9,7 @@ from virtualenv.create.via_global_ref.builtin.ref import (
|
||||
RefMust,
|
||||
RefWhen,
|
||||
)
|
||||
-from virtualenv.util.path import ensure_dir
|
||||
+from virtualenv.util.path import ensure_within
|
||||
|
||||
from .builtin_way import VirtualenvBuiltin
|
||||
|
||||
@@ -92,7 +92,7 @@ class ViaGlobalRefVirtualenvBuiltin(ViaGlobalRefApi, VirtualenvBuiltin, ABC):
|
||||
if any(i for i in dirs if i is not directory and directory.parts == i.parts[: len(directory.parts)]):
|
||||
dirs.remove(directory)
|
||||
for directory in sorted(dirs):
|
||||
- ensure_dir(directory)
|
||||
+ ensure_within(self.dest, directory)
|
||||
|
||||
self.set_pyenv_cfg()
|
||||
self.pyenv_cfg.write()
|
||||
diff --git a/src/virtualenv/create/via_global_ref/venv.py b/src/virtualenv/create/via_global_ref/venv.py
|
||||
index 8b704eb..4e4fc77 100644
|
||||
--- a/src/virtualenv/create/via_global_ref/venv.py
|
||||
+++ b/src/virtualenv/create/via_global_ref/venv.py
|
||||
@@ -9,7 +9,7 @@ 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, ensure_within
|
||||
+from virtualenv.util.path import ensure_within
|
||||
from virtualenv.util.subprocess import run_cmd
|
||||
|
||||
from .api import ViaGlobalRefApi, ViaGlobalRefMeta
|
||||
@@ -52,7 +52,8 @@ class Venv(ViaGlobalRefApi):
|
||||
def create(self) -> None:
|
||||
# stdlib venv writes interpreter files, configuration and activation scripts in place, so a planted symlink or
|
||||
# hard link would receive them outside the environment
|
||||
- ensure_within(self.dest, self.bin_dir)
|
||||
+ for directory in (self.bin_dir, self.purelib, self.platlib):
|
||||
+ ensure_within(self.dest, directory)
|
||||
for path in (self.dest / "pyvenv.cfg", self.dest / ".gitignore", *self.bin_dir.iterdir()):
|
||||
if path.is_symlink() or (path.is_file() and path.stat().st_nlink > 1):
|
||||
path.unlink()
|
||||
@@ -60,8 +61,6 @@ class Venv(ViaGlobalRefApi):
|
||||
self.create_inline()
|
||||
else:
|
||||
self.create_via_sub_process()
|
||||
- for lib in self.libs: # ty: ignore[not-iterable]
|
||||
- ensure_dir(lib)
|
||||
if self.describe is not None:
|
||||
self.describe.install_venv_shared_libs(self)
|
||||
super().create()
|
||||
diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py
|
||||
index bc02679..733a073 100644
|
||||
--- a/tests/unit/create/test_creator.py
|
||||
+++ b/tests/unit/create/test_creator.py
|
||||
@@ -220,6 +220,42 @@ def test_venv_creator_accepts_plain_destination(tmp_path: Path) -> None:
|
||||
assert (session.creator.dest / "pyvenv.cfg").is_file()
|
||||
|
||||
|
||||
+@pytest.mark.parametrize(
|
||||
+ "junction",
|
||||
+ [
|
||||
+ pytest.param(
|
||||
+ False, id="symlink", marks=pytest.mark.skipif(not fs_supports_symlink(), reason="symlinks are unavailable")
|
||||
+ ),
|
||||
+ pytest.param(True, id="junction", marks=pytest.mark.skipif(os.name != "nt", reason="Windows junctions")),
|
||||
+ ],
|
||||
+)
|
||||
+@pytest.mark.parametrize(
|
||||
+ "creator_name",
|
||||
+ [
|
||||
+ pytest.param("builtin", id="builtin"),
|
||||
+ pytest.param(
|
||||
+ "venv",
|
||||
+ id="venv",
|
||||
+ marks=pytest.mark.skipif("venv" not in CURRENT_CREATORS, reason="stdlib venv is unavailable"),
|
||||
+ ),
|
||||
+ ],
|
||||
+)
|
||||
+@pytest.mark.parametrize("folder", [pytest.param("bin_dir", id="executables"), pytest.param("purelib", id="packages")])
|
||||
+def test_creator_replaces_directory_links(tmp_path: Path, creator_name: str, folder: str, junction: bool) -> None:
|
||||
+ session: Final = session_via_cli([str(tmp_path / "env"), "--creator", creator_name, "--no-seed"])
|
||||
+ session.creator.dest.mkdir()
|
||||
+ stored: Final = session.creator.dest / "stored"
|
||||
+ stored.mkdir()
|
||||
+ link: Final = session.creator.dest / getattr(session.creator, folder).relative_to(session.creator.dest).parts[0]
|
||||
+ if junction:
|
||||
+ subprocess.run(["cmd", "/c", "mklink", "/J", str(link), str(stored)], capture_output=True, check=True)
|
||||
+ else:
|
||||
+ link.symlink_to(stored, target_is_directory=True)
|
||||
+ with session:
|
||||
+ session.run()
|
||||
+ assert (link.resolve(), session.creator.exe.is_file(), list(stored.iterdir())) == (link, True, [])
|
||||
+
|
||||
+
|
||||
CREATE_METHODS = []
|
||||
for k, v in CreatorSelector.for_interpreter(CURRENT).key_to_meta.items():
|
||||
if k in CURRENT_CREATORS:
|
||||
diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py
|
||||
index c4c448a..099798a 100644
|
||||
--- a/tests/unit/test_util.py
|
||||
+++ b/tests/unit/test_util.py
|
||||
@@ -2,14 +2,16 @@ from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
+import subprocess
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
import pytest
|
||||
|
||||
from virtualenv.app_data import _cache_dir_with_migration, _default_app_data_dir
|
||||
+from virtualenv.info import fs_supports_symlink
|
||||
from virtualenv.util.lock import ReentrantFileLock
|
||||
-from virtualenv.util.path import copy, write_file
|
||||
+from virtualenv.util.path import copy, ensure_within, write_file
|
||||
from virtualenv.util.subprocess import run_cmd
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -69,6 +71,56 @@ def test_write_file_replaces_content(tmp_path: Path, existing: bool) -> None:
|
||||
assert path.read_bytes() == b"generated"
|
||||
|
||||
|
||||
+@pytest.mark.parametrize("existing", [pytest.param(False, id="new"), pytest.param(True, id="existing")])
|
||||
+def test_environment_directories_create_nested_path(tmp_path: Path, existing: bool) -> None:
|
||||
+ folder: Final = tmp_path / "env" / "lib" / "python"
|
||||
+ if existing:
|
||||
+ folder.mkdir(parents=True)
|
||||
+ ensure_within(tmp_path / "env", folder)
|
||||
+ assert folder.is_dir()
|
||||
+
|
||||
+
|
||||
+def test_environment_directories_reject_regular_file(tmp_path: Path) -> None:
|
||||
+ folder: Final = tmp_path / "file"
|
||||
+ folder.touch()
|
||||
+ with pytest.raises(NotADirectoryError, match="not a real directory"):
|
||||
+ ensure_within(tmp_path, folder)
|
||||
+
|
||||
+
|
||||
+def test_environment_directories_reject_parent_components(tmp_path: Path) -> None:
|
||||
+ with pytest.raises(ValueError, match="must remain inside"):
|
||||
+ ensure_within(tmp_path, tmp_path / ".." / "folder")
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(not fs_supports_symlink(), reason="symlinks are unavailable")
|
||||
+@pytest.mark.parametrize(
|
||||
+ "existing", [pytest.param(False, id="missing-target"), pytest.param(True, id="existing-target")]
|
||||
+)
|
||||
+def test_environment_directories_remove_links(tmp_path: Path, caplog: pytest.LogCaptureFixture, existing: bool) -> None:
|
||||
+ target: Final = tmp_path / "stored"
|
||||
+ if existing:
|
||||
+ target.mkdir()
|
||||
+ link: Final = tmp_path / "link"
|
||||
+ link.symlink_to(target, target_is_directory=True)
|
||||
+ ensure_within(tmp_path, link / "nested")
|
||||
+ assert (
|
||||
+ link.is_symlink(),
|
||||
+ link.exists(),
|
||||
+ target.exists(),
|
||||
+ f"replace environment directory link {link}" in caplog.text,
|
||||
+ ) == (False, True, existing, True)
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skipif(os.name != "nt", reason="Windows junctions")
|
||||
+def test_environment_directories_report_replaced_junction(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
||||
+ target: Final = tmp_path / "stored"
|
||||
+ target.mkdir()
|
||||
+ link: Final = tmp_path / "link"
|
||||
+ subprocess.run(["cmd", "/c", "mklink", "/J", str(link), str(target)], check=True, capture_output=True)
|
||||
+ ensure_within(tmp_path, link)
|
||||
+ assert (link.resolve(), f"replace environment directory junction {link}" in caplog.text) == (link, True)
|
||||
+
|
||||
+
|
||||
def test_run_fail(tmp_path) -> None:
|
||||
code, out, err = run_cmd([str(tmp_path)])
|
||||
assert err
|
||||
--
|
||||
2.35.6
|
||||
@@ -27,6 +27,7 @@ SRC_URI += " \
|
||||
file://CVE-2026-113720.patch \
|
||||
file://CVE-2026-113719.patch \
|
||||
file://CVE-2026-113721.patch \
|
||||
file://CVE-2026-113716.patch \
|
||||
"
|
||||
|
||||
BBCLASSEXTEND = "native nativesdk"
|
||||
|
||||
Reference in new issue
Block a user