python3-virtualenv: fix CVE-2026-113725

This patch applies the upstream fix as referenced in [2], using the
commit shown in [1]. Escape the Bash prompt for both its backslash
decoding and expansion passes so backslashes cannot undo quoting.

[1] https://github.com/pypa/virtualenv/commit/c3104e02474b652832a9bbaae79f6e1696296932
[2] https://github.com/pypa/virtualenv/security/advisories/GHSA-vxj4-q4g9-crj7

Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Hetvi Thakar authored and Anuj Mittal committed 2026-11-09 08:59:13 +05:30
1 parent 46b8c6fa8f
commit e11dc337c9
2 files changed
+89

No files matched your search

@@ -0,0 +1,88 @@
From c3104e02474b652832a9bbaae79f6e1696296932 Mon Sep 17 00:00:00 2001
From: Bernát Gábor <gaborjbernat@gmail.com>
Date: Sun, 11 Oct 2026 17:36:53 -0700
Subject: [PATCH] fix(activation): escape the bash prompt for both PS1
passes (#3390)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
bash handles `PS1` in two passes each time it draws the prompt: it first
decodes backslash prompt escapes, turning `\\` into `\` and `\$` into
`$` (or `#` for root), and then expands the result. The bash `activate`
script escaped the environment name for the expansion pass only (#3326),
so a backslash in the name undoes that escaping. 🔐 A name such as
`a\$(touch PWNED)`, from the directory, `--prompt` or
`VIRTUALENV_PROMPT`, runs the command each time bash draws the prompt.
For root, `\$` decodes to `#`, so the same name renders wrong but runs
nothing.
Under bash, `activate` now escapes backslashes, dollar signs and
backticks a second time, so both passes leave the name literal. zsh does
no decode pass, so it keeps the single escaping added in 5b915367. The
prompt tests gain cases for a backslash before `$(...)` and before
backticks, and for names with a backslash before `$`, before a backtick,
doubled, and trailing; they render through bash 5.2 and zsh 5.9 and pass
as root and as an unprivileged user.
Every release up to 21.14.6 is affected, and earlier releases before
21.13.0 do not escape the name at all.
CVE: CVE-2026-113725
Upstream-Status: Backport [https://github.com/pypa/virtualenv/commit/c3104e02474b652832a9bbaae79f6e1696296932]
Backport Changes:
- Omit the release-only changelog fragment; the release documentation
directory is absent from the 21.2.1 source distribution.
Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
---
src/virtualenv/activation/bash/activate.sh | 8 +++++++-
tests/unit/activation/test_bash.py | 6 ++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/virtualenv/activation/bash/activate.sh b/src/virtualenv/activation/bash/activate.sh
index 0a372ff..1551d85 100644
--- a/src/virtualenv/activation/bash/activate.sh
+++ b/src/virtualenv/activation/bash/activate.sh
@@ -119,7 +119,13 @@ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
# bash, and zsh under PROMPT_SUBST, re-expand backslashes, dollar signs and backticks in PS1 on every redraw;
# zsh gets the escaping even without the option, since a user can set it after activation. The guards keep
# the substitution away from POSIX shells such as dash, which reject it with "Bad substitution"
- if [ -n "${BASH_VERSION-}${ZSH_VERSION-}" ]; then
+ if [ -n "${BASH_VERSION-}" ]; then
+ # bash first decodes its backslash prompt escapes, turning \\ into \ and \$ into $ (or # for root), and only
+ # then expands the result, so each character needs escaping for both passes
+ _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\\/\\\\\\\\}"
+ _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\$/\\\\\$}"
+ _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\`/\\\\\`}"
+ elif [ -n "${ZSH_VERSION-}" ]; then
_VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\\/\\\\}"
_VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\$/\\\$}"
_VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\`/\\\`}"
diff --git a/tests/unit/activation/test_bash.py b/tests/unit/activation/test_bash.py
index defd2ed..4512932 100644
--- a/tests/unit/activation/test_bash.py
+++ b/tests/unit/activation/test_bash.py
@@ -200,6 +200,8 @@ def prompt_after_activate(
pytest.param("x$(touch PWNED)y", "env", id="prompt-command-substitution"),
pytest.param("x`touch PWNED`y", "env", id="prompt-backticks"),
pytest.param("x${$(touch PWNED)}y", "env", id="prompt-parameter-expansion"),
+ pytest.param("x\\$(touch PWNED)y", "env", id="prompt-backslash-command-substitution"),
+ pytest.param("x\\`touch PWNED`y", "env", id="prompt-backslash-backticks"),
pytest.param(None, "x$(touch PWNED)y", id="dirname-command-substitution"),
],
)
@@ -217,6 +219,10 @@ def test_prompt_does_not_run_commands(
[
pytest.param("myenv", id="plain"),
pytest.param("a%n$HOME\\b`c`", id="special-characters"),
+ pytest.param("a\\$HOME", id="backslash-before-dollar"),
+ pytest.param("a\\`c`", id="backslash-before-backtick"),
+ pytest.param("x\\\\y", id="doubled-backslash"),
+ pytest.param("e\\", id="trailing-backslash"),
],
)
def test_prompt_renders_name_literally(
--
2.35.6
@@ -20,6 +20,7 @@ SRC_URI += " \
file://CVE-2026-113776.patch \
file://CVE-2026-113726-dependent.patch \
file://CVE-2026-113726.patch \
file://CVE-2026-113725.patch \
"
BBCLASSEXTEND = "native nativesdk"