mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-11-15 04:17:29 +00:00
python3-virtualenv: fix CVE-2026-113776
This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Replace activation-template placeholders in a single pass so placeholder names inside substituted paths or prompts cannot be expanded again as script content. [1] https://github.com/pypa/virtualenv/commit/602b26623a724827ff171bf11fef4ad186d01f74 [2] https://github.com/pypa/virtualenv/security/advisories/GHSA-8rjx-v5ww-45pp 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
ad862d2df7
commit
936c732252
2 files changed
+134
No files matched your search
@@ -0,0 +1,133 @@
|
||||
From 602b26623a724827ff171bf11fef4ad186d01f74 Mon Sep 17 00:00:00 2001
|
||||
From: Bernát Gábor <gaborjbernat@gmail.com>
|
||||
Date: Wed, 30 Sep 2026 23:01:05 -0700
|
||||
Subject: [PATCH] Merge commit from fork
|
||||
|
||||
instantiate_template replaced one placeholder at a time across the whole
|
||||
template, so a placeholder name inside an already quoted value, such as a
|
||||
parent directory named __VIRTUAL_NAME__, was rewritten from inside the
|
||||
finished literal. The second value's quotes closed the first, and the rest
|
||||
of the path ran as code in bash, fish, xonsh and activate_this.py; in csh
|
||||
the prompt display tokens broke activate.csh the same way.
|
||||
|
||||
Build every token's final text first and substitute them in one regex pass,
|
||||
with the csh display tokens joining that pass through substitutions().
|
||||
|
||||
CVE: CVE-2026-113776
|
||||
Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/602b26623a724827ff171bf11fef4ad186d01f74]
|
||||
|
||||
Backport Changes:
|
||||
- Omit the Csh display-token override and its specific test: 21.2.1 has
|
||||
no separate display-token substitution or prompt-literal helpers.
|
||||
Its Csh activator uses the corrected shared template substitution.
|
||||
- Exercise upstream's placeholder-in-parent-directory regression through
|
||||
the existing Bash relocation fixture, since 21.2.1 lacks the later
|
||||
prompt-test fixture. Add direct one-pass substitution checks for the
|
||||
existing Bash, Csh, Fish and Python activators, and retain the 21.2.1
|
||||
special-character fixture's other characters.
|
||||
|
||||
Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
|
||||
---
|
||||
src/virtualenv/activation/via_template.py | 21 +++++++++++++++++----
|
||||
tests/conftest.py | 3 ++-
|
||||
tests/unit/activation/test_activator.py | 16 ++++++++++++++++
|
||||
tests/unit/activation/test_bash.py | 1 +
|
||||
4 files changed, 36 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py
|
||||
index 926c2b0..3af9dc5 100644
|
||||
--- a/src/virtualenv/activation/via_template.py
|
||||
+++ b/src/virtualenv/activation/via_template.py
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
+import re
|
||||
import shlex
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -84,10 +85,22 @@ class ViaTemplateActivator(Activator, ABC):
|
||||
# read content as binary to avoid platform specific line normalization (\n -> \r\n)
|
||||
binary = read_binary(self.__module__, template)
|
||||
text = binary.decode("utf-8", errors="strict")
|
||||
- for key, value in replacements.items():
|
||||
- value_uni = self._repr_unicode(creator, value)
|
||||
- text = text.replace(key, self.quote(value_uni))
|
||||
- return text
|
||||
+ substitutions = self.substitutions(replacements, creator)
|
||||
+ # one pass over the template, so a placeholder name inside an already quoted value (a path component named
|
||||
+ # __VIRTUAL_NAME__) stays data instead of being rewritten by a later key from inside the finished literal
|
||||
+ pattern = re.compile("|".join(re.escape(key) for key in sorted(substitutions, key=len, reverse=True)))
|
||||
+ return pattern.sub(lambda match: substitutions[match.group()], text)
|
||||
+
|
||||
+ def substitutions(self, replacements: dict[str, str], creator: Creator) -> dict[str, str]:
|
||||
+ """Map each placeholder to the final text that replaces it in the template.
|
||||
+
|
||||
+ :param replacements: the raw placeholder values
|
||||
+ :param creator: the creator of the environment
|
||||
+
|
||||
+ :returns: the placeholder to substituted text mapping
|
||||
+
|
||||
+ """
|
||||
+ return {key: self.quote(self._repr_unicode(creator, value)) for key, value in replacements.items()}
|
||||
|
||||
@staticmethod
|
||||
def _repr_unicode(creator: Creator, value: str) -> str: # noqa: ARG004
|
||||
diff --git a/tests/conftest.py b/tests/conftest.py
|
||||
index 3ec4147..f27c450 100644
|
||||
--- a/tests/conftest.py
|
||||
+++ b/tests/conftest.py
|
||||
@@ -279,7 +279,8 @@ def is_inside_ci():
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def special_char_name():
|
||||
- base = "'\";&&e-$ èрт🚒♞中片-j"
|
||||
+ # __VIRTUAL_NAME__ is a template placeholder, and must stay data when the path embeds it
|
||||
+ base = "'\";&&e-$ __VIRTUAL_NAME__èрт🚒♞中片-j"
|
||||
if IS_WIN:
|
||||
# get rid of invalid characters on Windows
|
||||
base = base.replace('"', "")
|
||||
diff --git a/tests/unit/activation/test_activator.py b/tests/unit/activation/test_activator.py
|
||||
index 21be96d..03481c2 100644
|
||||
--- a/tests/unit/activation/test_activator.py
|
||||
+++ b/tests/unit/activation/test_activator.py
|
||||
@@ -4,9 +4,25 @@ from argparse import Namespace
|
||||
|
||||
import pytest
|
||||
|
||||
+from virtualenv.activation import BashActivator, CShellActivator, FishActivator, PythonActivator
|
||||
from virtualenv.activation.activator import Activator
|
||||
|
||||
|
||||
+@pytest.mark.parametrize("activator_class", [BashActivator, CShellActivator, FishActivator, PythonActivator])
|
||||
+def test_template_substitution_does_not_expand_replacement_values(monkeypatch, activator_class) -> None:
|
||||
+ template = b"__VIRTUAL_ENV__ __VIRTUAL_NAME__"
|
||||
+ monkeypatch.setattr("virtualenv.activation.via_template.read_binary", lambda *_: template)
|
||||
+ activator = activator_class(Namespace(prompt=None))
|
||||
+ replacements = {
|
||||
+ "__VIRTUAL_ENV__": "A__VIRTUAL_NAME__B/x'$(touch PWNED)'y",
|
||||
+ "__VIRTUAL_NAME__": "x'$(touch PWNED)'y",
|
||||
+ }
|
||||
+
|
||||
+ actual = activator.instantiate_template(replacements, "unused", None)
|
||||
+
|
||||
+ assert actual == " ".join(activator.quote(value) for value in replacements.values())
|
||||
+
|
||||
+
|
||||
@pytest.mark.graalpy
|
||||
def test_activator_prompt_cwd(monkeypatch, tmp_path) -> None:
|
||||
class FakeActivator(Activator):
|
||||
diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py
|
||||
index 00ac55f..df3aaf3 100644
|
||||
--- a/tests/unit/activation/test_bash.py
|
||||
+++ b/tests/unit/activation/test_bash.py
|
||||
@@ -130,6 +130,7 @@ def test_bash_activate_relocation_resolves_virtual_env(
|
||||
pytest.param("x'$(id > PWNED)'y", id="command-substitution"),
|
||||
pytest.param("x'`id > PWNED`'y", id="backticks"),
|
||||
pytest.param("a';id > PWNED;'b", id="statement-separator"),
|
||||
+ pytest.param("A__VIRTUAL_NAME__B/x'$(touch PWNED)'y", id="placeholder-in-parent-dir"),
|
||||
],
|
||||
)
|
||||
def test_bash_activate_relocation_does_not_run_path_commands(
|
||||
--
|
||||
2.35.6
|
||||
@@ -17,6 +17,7 @@ SRC_URI += " \
|
||||
file://CVE-2026-102925.patch \
|
||||
file://CVE-2026-113732_p1.patch \
|
||||
file://CVE-2026-113732_p2.patch \
|
||||
file://CVE-2026-113776.patch \
|
||||
"
|
||||
|
||||
BBCLASSEXTEND = "native nativesdk"
|
||||
|
||||
Reference in new issue
Block a user