Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -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 <packages@deepin.org> Wed, 07 May 2026 23:25:00 +0800

python-virtualenv (20.25.1+ds-1) unstable; urgency=medium

* New upstream point release.
Expand Down
103 changes: 103 additions & 0 deletions debian/patches/CVE-2026-22702.patch
Original file line number Diff line number Diff line change
@@ -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()

1 change: 1 addition & 0 deletions debian/patches/series
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
CVE-2026-22702.patch
debian_wheel_location.patch
debian_update_for_available_wheels.patch
disable-periodic-update.patch
Expand Down
Loading