VYPR
Moderate severityOSV Advisory· Published Dec 16, 2025· Updated Dec 17, 2025

filelock has TOCTOU race condition that allows symlink attacks during lock file creation

CVE-2025-68146

Description

filelock is a platform-independent file lock for Python. In versions prior to 3.20.1, a Time-of-Check-Time-of-Use (TOCTOU) race condition allows local attackers to corrupt or truncate arbitrary user files through symlink attacks. The vulnerability exists in both Unix and Windows lock file creation where filelock checks if a file exists before opening it with O_TRUNC. An attacker can create a symlink pointing to a victim file in the time gap between the check and open, causing os.open() to follow the symlink and truncate the target file. All users of filelock on Unix, Linux, macOS, and Windows systems are impacted. The vulnerability cascades to dependent libraries. The attack requires local filesystem access and ability to create symlinks (standard user permissions on Unix; Developer Mode on Windows 10+). Exploitation succeeds within 1-3 attempts when lock file paths are predictable. The issue is fixed in version 3.20.1. If immediate upgrade is not possible, use SoftFileLock instead of UnixFileLock/WindowsFileLock (note: different locking semantics, may not be suitable for all use cases); ensure lock file directories have restrictive permissions (chmod 0700) to prevent untrusted users from creating symlinks; and/or monitor lock file directories for suspicious symlinks before running trusted applications. These workarounds provide only partial mitigation. The race condition remains exploitable. Upgrading to version 3.20.1 is strongly recommended.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
filelockPyPI
< 3.20.13.20.1

Affected products

1

Patches

1
4724d7f8c339

Fix TOCTOU symlink vulnerability in lock file creation (#461)

https://github.com/tox-dev/filelockBernát GáborDec 15, 2025via ghsa
2 files changed · +39 1
  • src/filelock/_unix.py+1 1 modified
    @@ -38,7 +38,7 @@ class UnixFileLock(BaseFileLock):
     
             def _acquire(self) -> None:
                 ensure_directory_exists(self.lock_file)
    -            open_flags = os.O_RDWR | os.O_TRUNC
    +            open_flags = os.O_RDWR | os.O_TRUNC | os.O_NOFOLLOW
                 if not Path(self.lock_file).exists():
                     open_flags |= os.O_CREAT
                 fd = os.open(self.lock_file, open_flags, self._context.mode)
    
  • src/filelock/_windows.py+38 0 modified
    @@ -11,14 +11,52 @@
     from ._util import ensure_directory_exists, raise_on_not_writable_file
     
     if sys.platform == "win32":  # pragma: win32 cover
    +    import ctypes
         import msvcrt
    +    from ctypes import wintypes
    +
    +    # Windows API constants for reparse point detection
    +    FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
    +    INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
    +
    +    # Load kernel32.dll
    +    _kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
    +    _kernel32.GetFileAttributesW.argtypes = [wintypes.LPCWSTR]
    +    _kernel32.GetFileAttributesW.restype = wintypes.DWORD
    +
    +    def _is_reparse_point(path: str) -> bool:
    +        """
    +        Check if a path is a reparse point (symlink, junction, etc.) on Windows.
    +
    +        :param path: Path to check
    +        :return: True if path is a reparse point, False otherwise
    +        :raises OSError: If GetFileAttributesW fails for reasons other than file-not-found
    +        """
    +        attrs = _kernel32.GetFileAttributesW(path)
    +        if attrs == INVALID_FILE_ATTRIBUTES:
    +            # File doesn't exist yet - that's fine, we'll create it
    +            err = ctypes.get_last_error()
    +            if err == 2:  # noqa: PLR2004  # ERROR_FILE_NOT_FOUND
    +                return False
    +            if err == 3:  # noqa: PLR2004 # ERROR_PATH_NOT_FOUND
    +                return False
    +            # Some other error - let caller handle it
    +            return False
    +        return bool(attrs & FILE_ATTRIBUTE_REPARSE_POINT)
     
         class WindowsFileLock(BaseFileLock):
             """Uses the :func:`msvcrt.locking` function to hard lock the lock file on Windows systems."""
     
             def _acquire(self) -> None:
                 raise_on_not_writable_file(self.lock_file)
                 ensure_directory_exists(self.lock_file)
    +
    +            # Security check: Refuse to open reparse points (symlinks, junctions)
    +            # This prevents TOCTOU symlink attacks (CVE-TBD)
    +            if _is_reparse_point(self.lock_file):
    +                msg = f"Lock file is a reparse point (symlink/junction): {self.lock_file}"
    +                raise OSError(msg)
    +
                 flags = (
                     os.O_RDWR  # open for read and write
                     | os.O_CREAT  # create file if not exists
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.