File CVE-2026-22702.patch of Package python-virtualenv.42221
From b338d8cee138c2fd98f788ec295f8f2f719f53c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= <bgabor8@bloomberg.net>
Date: Fri, 9 Jan 2026 09:49:49 -0800
Subject: [PATCH] fix: resolve TOCTOU vulnerabilities in app_data and lock
directory creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use atomic os.makedirs(..., exist_ok=True) operations instead of
check-then-act pattern to prevent symlink-based TOCTOU attacks.
Reported by: tsigouris007
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
---
docs/changelog/3013.bugfix.rst | 1 +
src/virtualenv/app_data/__init__.py | 11 +++++------
src/virtualenv/util/lock.py | 7 +++----
3 files changed, 9 insertions(+), 10 deletions(-)
Index: virtualenv-20.22.0/src/virtualenv/app_data/__init__.py
===================================================================
--- virtualenv-20.22.0.orig/src/virtualenv/app_data/__init__.py
+++ virtualenv-20.22.0/src/virtualenv/app_data/__init__.py
@@ -36,12 +36,11 @@ 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)
+ 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)
if os.access(folder, os.W_OK):
return AppDataDiskFolder(folder)
Index: virtualenv-20.22.0/src/virtualenv/util/lock.py
===================================================================
--- virtualenv-20.22.0.orig/src/virtualenv/util/lock.py
+++ virtualenv-20.22.0/src/virtualenv/util/lock.py
@@ -5,7 +5,7 @@ from __future__ import annotations
import logging
import os
from abc import ABCMeta, abstractmethod
-from contextlib import contextmanager
+from contextlib import contextmanager, suppress
from pathlib import Path
from threading import Lock, RLock
@@ -15,11 +15,9 @@ from filelock import FileLock, Timeout
class _CountedFileLock(FileLock):
def __init__(self, lock_file):
parent = os.path.dirname(lock_file)
- if not os.path.isdir(parent):
- try:
- os.makedirs(parent)
- except OSError:
- pass
+ with suppress(OSError):
+ os.makedirs(parent, exist_ok=True)
+
super().__init__(lock_file)
self.count = 0
self.thread_safe = RLock()
@@ -113,10 +111,9 @@ class ReentrantFileLock(PathLockBase):
# multiple processes might be trying to get a first lock... so we cannot check if this directory exist without
# 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.
- try:
- os.makedirs(str(self.path))
- except OSError:
- pass
+ with suppress(OSError):
+ os.makedirs(str(self.path), exist_ok=True)
+
try:
lock.acquire(0.0001)
except Timeout:
Index: virtualenv-20.22.0/tests/conftest.py
===================================================================
--- virtualenv-20.22.0.orig/tests/conftest.py
+++ virtualenv-20.22.0/tests/conftest.py
@@ -23,8 +23,8 @@ def pytest_addoption(parser):
def pytest_configure(config):
"""Ensure randomly is called before we re-order"""
manager = config.pluginmanager
- # noinspection PyProtectedMember
- order = manager.hook.pytest_collection_modifyitems._nonwrappers
+
+ order = manager.hook.pytest_collection_modifyitems.get_hookimpls()
dest = next((i for i, p in enumerate(order) if p.plugin is manager.getplugin("randomly")), None)
if dest is not None:
from_pos = next(i for i, p in enumerate(order) if p.plugin is manager.getplugin(__file__))