From 0621daafdce42f4de720201ae679a9781cc6ace5 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 7 May 2026 23:21:25 +0800 Subject: [PATCH] fix(python-virtualenv): CVE-2024-53899 Fix command injection in activation scripts. Properly quote string placeholders in activation script templates to mitigate potential command injection vulnerability. Upstream: https://github.com/pypa/virtualenv/pull/2771 Upstream: https://github.com/pypa/virtualenv/releases/tag/20.26.6 Generated-By: glm-5.1 Co-Authored-By: hudeng --- debian/changelog | 9 + debian/patches/CVE-2024-53899.patch | 328 ++++++++++++++++++++++++++++ debian/patches/series | 1 + 3 files changed, 338 insertions(+) create mode 100644 debian/patches/CVE-2024-53899.patch diff --git a/debian/changelog b/debian/changelog index 3c36c68..662e8e2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +python-virtualenv (20.25.1+ds-1deepin1) unstable; urgency=medium + + * Fix CVE-2024-53899: Command injection in activation scripts. + Properly quote string placeholders in activation script templates + to mitigate potential command injection. + Upstream: https://github.com/pypa/virtualenv/pull/2771 + + -- deepin-ci-robot Wed, 07 May 2026 23:15:00 +0800 + python-virtualenv (20.25.1+ds-1) unstable; urgency=medium * New upstream point release. diff --git a/debian/patches/CVE-2024-53899.patch b/debian/patches/CVE-2024-53899.patch new file mode 100644 index 0000000..a083320 --- /dev/null +++ b/debian/patches/CVE-2024-53899.patch @@ -0,0 +1,328 @@ +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/bash/activate.sh +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/bash/activate.sh ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/bash/activate.sh +@@ -45,18 +45,18 @@ deactivate () { + # unset irrelevant variables + deactivate nondestructive + +-VIRTUAL_ENV='__VIRTUAL_ENV__' ++VIRTUAL_ENV=__VIRTUAL_ENV__ + if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then + VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV") + fi + export VIRTUAL_ENV + + _OLD_VIRTUAL_PATH="$PATH" +-PATH="$VIRTUAL_ENV/__BIN_NAME__:$PATH" ++PATH="$VIRTUAL_ENV/"__BIN_NAME__":$PATH" + export PATH + +-if [ "x__VIRTUAL_PROMPT__" != x ] ; then +- VIRTUAL_ENV_PROMPT="__VIRTUAL_PROMPT__" ++if [ "x"__VIRTUAL_PROMPT__ != x ] ; then ++ VIRTUAL_ENV_PROMPT=__VIRTUAL_PROMPT__ + else + VIRTUAL_ENV_PROMPT=$(basename "$VIRTUAL_ENV") + fi +@@ -84,4 +84,4 @@ pydoc () { + # The hash command must be called to get it to forget past + # commands. Without forgetting past commands the $PATH changes + # we made may not be respected +-hash -r 2>/dev/null ++hash -r 2>/dev/null || true +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/batch/__init__.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/batch/__init__.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/batch/__init__.py +@@ -15,6 +15,10 @@ class BatchActivator(ViaTemplateActivato + yield "deactivate.bat" + yield "pydoc.bat" + ++ @staticmethod ++ def quote(string): ++ return string ++ + def instantiate_template(self, replacements, template, creator): + # ensure the text has all newlines as \r\n - required by batch + base = super().instantiate_template(replacements, template, creator) +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/cshell/activate.csh +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/cshell/activate.csh ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/cshell/activate.csh +@@ -10,15 +10,15 @@ alias deactivate 'test $?_OLD_VIRTUAL_PA + # Unset irrelevant variables. + deactivate nondestructive + +-setenv VIRTUAL_ENV '__VIRTUAL_ENV__' ++setenv VIRTUAL_ENV __VIRTUAL_ENV__ + + set _OLD_VIRTUAL_PATH="$PATH:q" +-setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q" ++setenv PATH "$VIRTUAL_ENV:q/"__BIN_NAME__":$PATH:q" + + + +-if ('__VIRTUAL_PROMPT__' != "") then +- setenv VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__' ++if (__VIRTUAL_PROMPT__ != "") then ++ setenv VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__ + else + setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q" + endif +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/fish/activate.fish +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/fish/activate.fish ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/fish/activate.fish +@@ -58,20 +58,20 @@ end + # Unset irrelevant variables. + deactivate nondestructive + +-set -gx VIRTUAL_ENV '__VIRTUAL_ENV__' ++set -gx VIRTUAL_ENV __VIRTUAL_ENV__ + + # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling + if test (echo $FISH_VERSION | head -c 1) -lt 3 +- set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) ++ set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) + else + set -gx _OLD_VIRTUAL_PATH $PATH + end +-set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH ++set -gx PATH "$VIRTUAL_ENV"'/'__BIN_NAME__ $PATH + + # Prompt override provided? + # If not, just use the environment name. +-if test -n '__VIRTUAL_PROMPT__' +- set -gx VIRTUAL_ENV_PROMPT '__VIRTUAL_PROMPT__' ++if test -n __VIRTUAL_PROMPT__ ++ set -gx VIRTUAL_ENV_PROMPT __VIRTUAL_PROMPT__ + else + set -gx VIRTUAL_ENV_PROMPT (basename "$VIRTUAL_ENV") + end +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/nushell/activate.nu +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/nushell/activate.nu ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/nushell/activate.nu +@@ -17,7 +17,7 @@ export-env { + } | all {|i| $i == true} + } + +- # Emulates a `test -z`, but btter as it handles e.g 'false' ++ # Emulates a `test -z`, but better as it handles e.g 'false' + def is-env-true [name: string] { + if (has-env $name) { + # Try to parse 'true', '0', '1', and fail if not convertible +@@ -32,8 +32,8 @@ export-env { + } + } + +- let virtual_env = '__VIRTUAL_ENV__' +- let bin = '__BIN_NAME__' ++ let virtual_env = __VIRTUAL_ENV__ ++ let bin = __BIN_NAME__ + + let is_windows = ($nu.os-info.family) == 'windows' + let path_name = (if (has-env 'Path') { +@@ -47,10 +47,10 @@ export-env { + let new_path = ($env | get $path_name | prepend $venv_path) + + # If there is no default prompt, then use the env name instead +- let virtual_env_prompt = (if ('__VIRTUAL_PROMPT__' | is-empty) { ++ let virtual_env_prompt = (if (__VIRTUAL_PROMPT__ | is-empty) { + ($virtual_env | path basename) + } else { +- '__VIRTUAL_PROMPT__' ++ __VIRTUAL_PROMPT__ + }) + + let new_env = { +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/nushell/__init__.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/nushell/__init__.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/nushell/__init__.py +@@ -7,6 +7,25 @@ class NushellActivator(ViaTemplateActiva + def templates(self): + yield "activate.nu" + ++ @staticmethod ++ def quote(string): ++ """ ++ Nushell supports raw strings like: r###'this is a string'###. ++ ++ This method finds the maximum continuous sharps in the string and then ++ quote it with an extra sharp. ++ """ ++ max_sharps = 0 ++ current_sharps = 0 ++ for char in string: ++ if char == "#": ++ current_sharps += 1 ++ max_sharps = max(current_sharps, max_sharps) ++ else: ++ current_sharps = 0 ++ wrapping = "#" * (max_sharps + 1) ++ return f"r{wrapping}'{string}'{wrapping}" ++ + def replacements(self, creator, dest_folder): # noqa: ARG002 + return { + "__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt, +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/powershell/activate.ps1 +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/powershell/activate.ps1 ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/powershell/activate.ps1 +@@ -37,8 +37,8 @@ deactivate -nondestructive + $VIRTUAL_ENV = $BASE_DIR + $env:VIRTUAL_ENV = $VIRTUAL_ENV + +-if ("__VIRTUAL_PROMPT__" -ne "") { +- $env:VIRTUAL_ENV_PROMPT = "__VIRTUAL_PROMPT__" ++if (__VIRTUAL_PROMPT__ -ne "") { ++ $env:VIRTUAL_ENV_PROMPT = __VIRTUAL_PROMPT__ + } + else { + $env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf ) +@@ -46,7 +46,7 @@ else { + + New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH + +-$env:PATH = "$env:VIRTUAL_ENV/__BIN_NAME____PATH_SEP__" + $env:PATH ++$env:PATH = "$env:VIRTUAL_ENV/" + __BIN_NAME__ + __PATH_SEP__ + $env:PATH + if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { + function global:_old_virtual_prompt { + "" +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/powershell/__init__.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/powershell/__init__.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/powershell/__init__.py +@@ -7,6 +7,18 @@ class PowerShellActivator(ViaTemplateAct + def templates(self): + yield "activate.ps1" + ++ @staticmethod ++ def quote(string): ++ """ ++ This should satisfy PowerShell quoting rules [1], unless the quoted ++ string is passed directly to Windows native commands [2]. ++ ++ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules ++ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters ++ """ # noqa: D205 ++ string = string.replace("'", "''") ++ return f"'{string}'" ++ + + __all__ = [ + "PowerShellActivator", +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/python/activate_this.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/python/activate_this.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/python/activate_this.py +@@ -1,7 +1,8 @@ + """ + Activate virtualenv for current interpreter: + +-Use exec(open(this_file).read(), {'__file__': this_file}). ++import runpy ++runpy.run_path(this_file) + + This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. + """ # noqa: D415 +@@ -15,22 +16,22 @@ import sys + try: + abs_file = os.path.abspath(__file__) + except NameError as exc: +- msg = "You must use exec(open(this_file).read(), {'__file__': this_file})" ++ msg = "You must use import runpy; runpy.run_path(this_file)" + raise AssertionError(msg) from exc + + bin_dir = os.path.dirname(abs_file) +-base = bin_dir[: -len("__BIN_NAME__") - 1] # strip away the bin part from the __file__, plus the path separator ++base = bin_dir[: -len(__BIN_NAME__) - 1] # strip away the bin part from the __file__, plus the path separator + + # prepend bin to PATH (this file is inside the bin directory) + os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)]) + os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory +-os.environ["VIRTUAL_ENV_PROMPT"] = "__VIRTUAL_PROMPT__" or os.path.basename(base) # noqa: SIM222 ++os.environ["VIRTUAL_ENV_PROMPT"] = __VIRTUAL_PROMPT__ or os.path.basename(base) + + # add the virtual environments libraries to the host python import mechanism + prev_length = len(sys.path) +-for lib in "__LIB_FOLDERS__".split(os.pathsep): ++for lib in __LIB_FOLDERS__.split(os.pathsep): + path = os.path.realpath(os.path.join(bin_dir, lib)) +- site.addsitedir(path.decode("utf-8") if "__DECODE_PATH__" else path) ++ site.addsitedir(path.decode("utf-8") if __DECODE_PATH__ else path) + sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] + + sys.real_prefix = sys.prefix +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/python/__init__.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/python/__init__.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/python/__init__.py +@@ -10,10 +10,14 @@ class PythonActivator(ViaTemplateActivat + def templates(self): + yield "activate_this.py" + ++ @staticmethod ++ def quote(string): ++ return repr(string) ++ + def replacements(self, creator, dest_folder): + replacements = super().replacements(creator, dest_folder) + lib_folders = OrderedDict((os.path.relpath(str(i), str(dest_folder)), None) for i in creator.libs) +- lib_folders = os.pathsep.join(lib_folders.keys()).replace("\\", "\\\\") # escape Windows path characters ++ lib_folders = os.pathsep.join(lib_folders.keys()) + replacements.update( + { + "__LIB_FOLDERS__": lib_folders, +Index: github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/via_template.py +=================================================================== +--- github-python-virtualenv-CVE-2024-53899.orig/src/virtualenv/activation/via_template.py ++++ github-python-virtualenv-CVE-2024-53899/src/virtualenv/activation/via_template.py +@@ -1,6 +1,7 @@ + from __future__ import annotations + + import os ++import shlex + import sys + from abc import ABC, abstractmethod + +@@ -21,6 +22,16 @@ class ViaTemplateActivator(Activator, AB + def templates(self): + raise NotImplementedError + ++ @staticmethod ++ def quote(string): ++ """ ++ Quote strings in the activation script. ++ ++ :param string: the string to quote ++ :return: quoted string that works in the activation script ++ """ ++ return shlex.quote(string) ++ + def generate(self, creator): + dest_folder = creator.bin_dir + replacements = self.replacements(creator, dest_folder) +@@ -47,8 +58,10 @@ class ViaTemplateActivator(Activator, AB + # errors when the dest is not writable + if dest.exists(): + dest.unlink() ++ # Powershell assumes Windows 1252 encoding when reading files without BOM ++ encoding = "utf-8-sig" if str(template).endswith(".ps1") else "utf-8" + # use write_bytes to avoid platform specific line normalization (\n -> \r\n) +- dest.write_bytes(text.encode("utf-8")) ++ dest.write_bytes(text.encode(encoding)) + generated.append(dest) + return generated + +@@ -61,7 +74,7 @@ class ViaTemplateActivator(Activator, AB + text = binary.decode("utf-8", errors="strict") + for key, value in replacements.items(): + value_uni = self._repr_unicode(creator, value) +- text = text.replace(key, value_uni) ++ text = text.replace(key, self.quote(value_uni)) + return text + + @staticmethod diff --git a/debian/patches/series b/debian/patches/series index 60b3c8a..e5600da 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,4 @@ +CVE-2024-53899.patch debian_wheel_location.patch debian_update_for_available_wheels.patch disable-periodic-update.patch