From 13a4dd269aab642349f38f33eaacabde941c4ff4 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 7 May 2026 23:23:17 +0800 Subject: [PATCH] fix(python-virtualenv): CVE-2026-22702 Fix TOCTOU race condition in directory creation. Fix TOCTOU vulnerabilities in app_data and lock directory creation that could be exploited via symlink attacks. Upstream: https://github.com/pypa/virtualenv/pull/3013 Upstream: https://github.com/pypa/virtualenv/releases/tag/20.36.1 Generated-By: glm-5.1 Co-Authored-By: hudeng --- debian/changelog | 9 +++ debian/patches/CVE-2026-22702.patch | 103 ++++++++++++++++++++++++++++ debian/patches/series | 1 + 3 files changed, 113 insertions(+) create mode 100644 debian/patches/CVE-2026-22702.patch diff --git a/debian/changelog b/debian/changelog index 3c36c68..f62c5c8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +python-virtualenv (20.25.1+ds-1deepin1) unstable; urgency=medium + + * Fix CVE-2026-22702: TOCTOU race condition in directory creation. + Fix TOCTOU vulnerabilities in app_data and lock directory creation + that could be exploited via symlink attacks. + Upstream: https://github.com/pypa/virtualenv/pull/3013 + + -- deepin-ci-robot Wed, 07 May 2026 23:25:00 +0800 + python-virtualenv (20.25.1+ds-1) unstable; urgency=medium * New upstream point release. diff --git a/debian/patches/CVE-2026-22702.patch b/debian/patches/CVE-2026-22702.patch new file mode 100644 index 0000000..f28419c --- /dev/null +++ b/debian/patches/CVE-2026-22702.patch @@ -0,0 +1,103 @@ +Index: github-python-virtualenv-CVE-2026-22702/src/virtualenv/app_data/__init__.py +=================================================================== +--- github-python-virtualenv-CVE-2026-22702.orig/src/virtualenv/app_data/__init__.py ++++ github-python-virtualenv-CVE-2026-22702/src/virtualenv/app_data/__init__.py +@@ -12,6 +12,8 @@ from .read_only import ReadOnlyAppData + from .via_disk_folder import AppDataDiskFolder + from .via_tempdir import TempAppData + ++LOGGER = logging.getLogger(__name__) ++ + + def _default_app_data_dir(env): + key = "VIRTUALENV_OVERRIDE_APP_DATA" +@@ -34,16 +36,15 @@ def make_app_data(folder, **kwargs): + if is_read_only: + return ReadOnlyAppData(folder) + +- if not os.path.isdir(folder): +- try: +- os.makedirs(folder) +- logging.debug("created app data folder %s", folder) +- except OSError as exception: +- logging.info("could not create app data folder %s due to %r", folder, exception) ++ try: ++ os.makedirs(folder, exist_ok=True) ++ LOGGER.debug("created app data folder %s", folder) ++ except OSError as exception: ++ LOGGER.info("could not create app data folder %s due to %r", folder, exception) + + if os.access(folder, os.W_OK): + return AppDataDiskFolder(folder) +- logging.debug("app data folder %s has no write access", folder) ++ LOGGER.debug("app data folder %s has no write access", folder) + return TempAppData() + + +Index: github-python-virtualenv-CVE-2026-22702/src/virtualenv/util/lock.py +=================================================================== +--- github-python-virtualenv-CVE-2026-22702.orig/src/virtualenv/util/lock.py ++++ github-python-virtualenv-CVE-2026-22702/src/virtualenv/util/lock.py +@@ -11,13 +11,14 @@ from threading import Lock, RLock + + from filelock import FileLock, Timeout + ++LOGGER = logging.getLogger(__name__) ++ + + class _CountedFileLock(FileLock): + def __init__(self, lock_file) -> None: + parent = os.path.dirname(lock_file) +- if not os.path.isdir(parent): +- with suppress(OSError): +- os.makedirs(parent) ++ with suppress(OSError): ++ os.makedirs(parent, exist_ok=True) + + super().__init__(lock_file) + self.count = 0 +@@ -27,16 +28,22 @@ class _CountedFileLock(FileLock): + if not self.thread_safe.acquire(timeout=-1 if timeout is None else timeout): + raise Timeout(self.lock_file) + if self.count == 0: +- super().acquire(timeout, poll_interval) ++ try: ++ super().acquire(timeout, poll_interval) ++ except BaseException: ++ self.thread_safe.release() ++ raise + self.count += 1 + + def release(self, force=False): # noqa: FBT002 + with self.thread_safe: + if self.count > 0: +- self.thread_safe.release() +- if self.count == 1: +- super().release(force=force) +- self.count = max(self.count - 1, 0) ++ if self.count == 1: ++ super().release(force=force) ++ self.count -= 1 ++ if self.count == 0: ++ # if we have no more users of this lock, release the thread lock ++ self.thread_safe.release() + + + _lock_store = {} +@@ -109,14 +116,14 @@ class ReentrantFileLock(PathLockBase): + # a lock, but that lock might then become expensive, and it's not clear where that lock should live. + # Instead here we just ignore if we fail to create the directory. + with suppress(OSError): +- os.makedirs(str(self.path)) ++ os.makedirs(str(self.path), exist_ok=True) + + try: + lock.acquire(0.0001) + except Timeout: + if no_block: + raise +- logging.debug("lock file %s present, will block until released", lock.lock_file) ++ LOGGER.debug("lock file %s present, will block until released", lock.lock_file) + lock.release() # release the acquire try from above + lock.acquire() + diff --git a/debian/patches/series b/debian/patches/series index 60b3c8a..ed1c645 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,3 +1,4 @@ +CVE-2026-22702.patch debian_wheel_location.patch debian_update_for_available_wheels.patch disable-periodic-update.patch