Oct 23, 2026·5 min read·3 visits
Local unprivileged users can manipulate lock directories created by filelock's ReadWriteLock, causing victim processes to recursively delete arbitrary directories using path-based shutil.rmtree calls.
A Time-of-Check Time-of-Use (TOCTOU) race condition in the Python `filelock` library (versions 4.1.0 to 4.2.0) allows local unprivileged attackers to cause arbitrary directory deletion by swapping file system targets during lock cleanup or dead owner sweeps.
The filelock package is a platform-independent Python implementation used to manage inter-process file locks. Starting with version 4.1.0, the package introduced SQLite-backed reader/writer lock primitives, implemented in ReadWriteLock and AsyncReadWriteLock. These classes allow multiple concurrent readers or an exclusive writer to synchronize operations across independent operating system processes.
When operating in fallback environments where direct file descriptor interaction via /proc/self/fd is unavailable or when utilizing shared directories, ReadWriteLock creates temporary private directories formatted as .filelock-* in the database parent directory. These directories hold hard-linked entries (lock.db) corresponding to active process lock instances.
Prior to version 4.2.1, releasing a lock or cleaning up stale lock entries during dead-owner sweeps triggered recursive path-based deletion via shutil.rmtree(). In non-sticky, world-writable directories, a local attacker can exploit this design by swapping the expected directory path with a target victim directory prior to deletion.
The technical root cause of CVE-2026-112937 lies in relying on path string resolution during recursive directory cleanup instead of using file descriptor pinning. In src/filelock/_read_write.py, the lock mechanism invoked shutil.rmtree(directory) with a string path representing .filelock-<pid>-<hash>.
Because string-based path traversal performs resolution dynamically at execution time, a window of exposure exists between when the lock directory is identified and when shutil.rmtree() performs recursive directory unlinking. An attacker with write and rename permissions in the shared parent directory can move the legitimate .filelock-* directory and replace it with a directory owned by the victim process.
When the victim process finishes lock release or executes the _sweep_links_of_the_dead routine, it passes the string path to shutil.rmtree(). Python traverses the substituted directory path using the process's effective permissions, systematically unlinking all child items and removing the directory hierarchy.
In vulnerable versions, link directories were unlinked directly via shutil.rmtree without verifying directory inode stability or permissions:
# Vulnerable implementation in filelock/_read_write.py
def _purge_link_directory(self, directory: str) -> None:
# Vulnerable path string resolution
if os.path.exists(directory):
shutil.rmtree(directory)The fix implemented in commit 5e3f2ec0c5fb17cb2635b60b2b68bc63ab81ca2d replaces path-based deletion with file descriptor-pinned operations through _remove_link_directory(link). The patched implementation opens the target directory directly and verifies its device and inode identifier:
# Patched implementation in filelock/_read_write.py
def _remove_link_directory(link: Link) -> None:
# Open directory file descriptor securely
fd = os.open(link.path, _DIRECTORY_FLAGS)
try:
opened = os.fstat(fd)
# Verify device and inode match expected identity
if (opened.st_dev, opened.st_ino) != link.identity:
raise SecurityError("Directory identity mismatch detected")
# Perform directory-relative unlinks using dir_fd
_unlink_contents_at(fd)
finally:
os.close(fd)Additionally, filelock introduced _names_are_private and _directory_is_private helpers to ensure parent directories restrict write access strictly to the owner before link operations proceed. Sweeping functions now enforce user identity checks via os.fstat(fd).st_uid == owner.
To exploit this vulnerability, an attacker requires local unprivileged shell access and write access to the parent directory where the filelock database resides. Shared temporary locations without sticky bits (such as unprotected custom subdirectories under /tmp) represent primary attack vectors.
The attack flow operates in four distinct stages:
.filelock-* entries created by the victim process.rename) to move .filelock-<pid>-<hash> to a temporary name..filelock-<pid>-<hash> pointing to a victim-owned target directory (e.g., /home/victim/project).shutil.rmtree(), traversing the attacker-placed reference and deleting the target directory tree.The impact of CVE-2026-112937 is restricted to localized integrity loss and application denial of service. The vulnerability does not provide arbitrary code execution or direct read access to confidential file contents.
Because the deletion occurs under the user privileges running the filelock library, an attacker can induce destruction of any file or directory writable by that user account. If the victim process runs as a privileged account (such as root or Administrator), an unprivileged attacker can delete critical system configurations or database directories.
This vulnerability has been assigned a CVSS v3.1 score of 6.1 (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L). EPSS scoring is rated at 0.00096, reflecting no observed exploitation in wild ransomware or automated attack frameworks.
Organizations and developers should update the filelock dependency to version 4.2.1 or higher immediately.
For Python environments managed via pip:
pip install --upgrade "filelock>=4.2.1"For projects using pyproject.toml dependencies:
[dependencies]
filelock = ">=4.2.1"If immediate dependency updates cannot be deployed, teams must apply filesystem configuration workarounds. Ensure all lock databases and link directories reside inside restricted directories owned by the process user with strict permissions (0o700). Avoid placing lock instances in shared world-writable paths like /tmp unless sticky bits (S_ISVTX) are strictly enforced.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
filelock tox-dev | >= 4.1.0, < 4.2.1 | 4.2.1 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-112937 |
| Weakness Class | CWE-367: Time-of-Check Time-of-Use (TOCTOU) Race Condition |
| CVSS v3.1 Score | 6.1 (Medium) |
| CVSS Vector | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L |
| Attack Vector | Local (Unprivileged) |
| Exploit Maturity | None (No public weaponized exploit) |
| CISA KEV Status | Not Listed |
The software checks the state of a resource before acting upon it, but the resource's state changes before the action is executed.
A local race condition and mutual exclusion bypass vulnerability exists in filelock versions 4.1.0 through 4.2.0. On Linux filesystems with rapid inode reuse, a local attacker can manipulate modification timestamps and lock files to cause waiting processes to falsely evict active soft lock markers.
vLLM prior to version 0.31.0 contains an uncontrolled resource consumption vulnerability (CWE-400) in its HTTP API request handling logic. Unauthenticated remote attackers can transmit per-request keyword arguments (`mm_processor_kwargs` and `media_io_kwargs`) that are passed directly into internal multimodal preprocessing pipelines, causing CPU/GPU memory exhaustion and crashing the engine process.
An insufficient verification of data authenticity vulnerability in SiYuan prior to version 3.7.4 allows an attacker to overwrite arbitrary installed marketplace packages (plugins, widgets, themes, and templates). By providing a mismatched package name and package archive repository URL during a Bazaar installation request, arbitrary file write into trusted plugin directories can be achieved, enabling persistence and execution across application restarts.
In obsidian-tc versions 1.0.1 through 1.31.3, memory lifecycle tools fail to validate note ownership prior to rematerializing Markdown files and execute non-atomic SQLite database mutations. An authorized caller can overwrite arbitrary note bodies on disk and cause persistent desynchronization between the database memory graph and physical vault files.
A high-severity authorization bypass vulnerability in obsidian-tc allows clients to bypass Human-In-The-Loop (HITL) confirmation prompts over Streamable HTTP deployments. Due to incomplete validation of returned prompt responses, an attacker echoing a signed requestState token can force execution of destructive vault operations even if a human user declines or cancels the prompt.
A Denial of Service (DoS) vulnerability in pypdf versions prior to 6.21.0 allows remote attackers to cause high CPU and memory resource exhaustion by supplying PDF files containing oversized or deeply nested arrays, dictionaries, or content streams.