GitHub Advisoryadvisory · PyPI
GHSA-9c4x-gm8v-wgc4 filelock: StrictSoftFileLock and SoftReadWriteLock adopt a protocol directory another user created first, losing mutual exclusion
MEDIUM
Official page ↗
- Aliases
- CVE-2026-112911
- Ecosystem
- PyPI
- Packages
- filelock
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H
- Weaknesses
- CWE-283
- Reviewed
- 2026-10-28
Affected packages
• PyPI filelock: >= 3.30.0, < 4.2.1 → fixed in 4.2.1
Description
`StrictSoftFileLock`, `AsyncStrictSoftFileLock`, `SoftReadWriteLock` and `AsyncSoftReadWriteLock` keep their whole ownership state as files in a directory derived from the lock path (`.filelock/claims`, `.rw/{gen,holders}`). When that directory already exists they adopt it after checking only that it is a real directory, not who owns it. Another local user who can create names in the lock's directory can create the tree before the first acquire on that path by the user running the lock (below: the lock user). In `/tmp`, `/var/tmp` or another sticky shared directory any user can do that; the sticky bit is a directory flag that lets only a file's owner remove or rename it. The other user then owns the tree. They can unlink the lock user's claims while the lock user holds the lock, so a second process of the lock user, root included, enters the same critical section and neither process sees an error.
Against `SoftReadWriteLock` the same position also admits two writers with `on_compromise` silent. It redirects the lock user's own creates and renames into a directory the other user picks (the scanner replaced a repository's `.git/HEAD` with a filelock record written with the lock user's privileges). It also keeps the lock user's acquire or release running past any `timeout`. Affected versions are filelock 3.30.0 through 4.2.0 (finding `ANT-2026-Q5VK5JEE`).
Severity
CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H` scores 6.3 (Medium). `AV:L` (local): the other user needs an account on the same host. `PR:L` (low privileges): an ordinary account is enough. `UI:N` (no user interaction). `AC:H` (high attack complexity): the other user must create the protocol directory before the lock user's first acquire on that path. That depends on the path being new, or on a reboot, a tmp cleaner or the documented `.rw` recovery having removed its tree; once planted, the rest needs no race. `I:H` (high integrity impact): two holders write whatever the lock protects, and for `SoftReadWriteLock` the lock user's process creates, replaces or deletes files named `HEAD`, `epoch` and `0000000000000000000N` in any directory the lock user can write. `A:H` (high availability impact): the owner of `.rw` can make the lock user's `read_lock(timeout=1)` spin at full CPU without end, cost 22 s per acquire, or pin 1 MiB of memory per planted file until a heartbeat thread dies of `MemoryError`. `C:N` (no confidentiality impact). `S:U` (scope unchanged).
Classification from the review: security, conditional on the other user winning initial ownership of the protocol directory. The scanner rated it high (CWE-283). With `AC:L` the same impacts would score 7.1.
Scope and preconditions
• The other user can create names in the directory that holds the lock. In a sticky `1777` directory such as `/tmp` any local user can. The sticky bit stops that user from renaming or removing a directory the lock user created, but does nothing for one the other user created first.
• The other user gets there first: `.filelock` (strict) or `.rw` (soft read-write) must not exist when the lock user's process first acquires. `_strict.py` does not remove the directory at any point, so a path the lock user has already used stays safe for as long as the tree exists. The exposure is a new lock path, or one whose tree a reboot, a tmp cleaner, a cache purge or the recovery described in `docs/how-to.rst` removed.
• For the strict lock the planted directory must be writable by the lock user (`0777`); a planted `0755` directory makes the lock user's acquire fail with `PermissionError` instead. Planting only the outer `.filelock` is enough. The lock user creates `claims` inside it, and the other user, as owner of the parent, renames that `claims` aside and creates an empty one.
• The Linux `fs.protected_regular`, `fs.protected_symlinks`, `fs.protected_hardlinks` and `fs.protected_fifos` sysctls (kernel settings, all enabled in the scanner's image) do not interfere. They cover regular files, symlinks, hard links and FIFOs, not directories. The `SoftReadWriteLock` symlink redirect below also passes `fs.protected_symlinks=1`, because the symlink (a file that points at another path) sits in the other user's own non-sticky directory.
• The affected classes are `StrictSoftFileLock` and `AsyncStrictSoftFileLock` (which inherits `_acquire`), and `SoftReadWriteLock` and `AsyncSoftReadWriteLock` with default options. `FileLock` gives way to the same position (when the other user creates the lock file first with mode `0666` and unlinks it during a hold, a second lock user process acquires), so the strict lock is not weaker than the native one here. This advisory reports it because `StrictSoftFileLock` is the lock documented as fail-closed and already raises `SoftFileLockProtocolError` for a symlink or a regular file at the same names, while it uses a directory that belongs to someone else without any signal. The `SoftReadWriteLock` half has no such counterpart: its redirected creates, renames and unlinks land outside the lock's own files.
• A non-sticky directory writable by other users is outside the boundary below. There anyone who can create the directory can also rename the lock user's own tree aside, and no check inside the lock can tell that from a first use.
These protections against another local user hold only when no other user can replace the lock's directory or any of its ancestors. That holds when each ancestor belongs to the user or root and is either not writable by others or sticky. A user who can rename entries in a non-sticky directory above the lock controls every name below it, which no file-based lock can defend against.
Affected code
At the scanned commit b831496: `_ensure_protocol_directory` returns for any existing real directory, whatever its owner or mode. `StrictSoftFileLock._acquire` calls it for both levels and does not look at directory ownership again. `_attempt_doorway` decides from the claim scan alone, and `_unlink_owner_path_result` counts a missing claim as removed on release. The sibling site is `OsFiles.prepare`. Its `mkdir(mode=0o700)` takes effect only when it creates the directory, and its `lstat` check reads neither `st_uid` nor the permission bits. It runs once per instance, and every later `OsFiles` method takes a full path, with `O_NOFOLLOW` guarding only the last component.
Introduced
The strict site came with the claims protocol in 650c17c79c54 ("feat(strict): replace shared markers with owner claims", #659, 2026-07-14), first released in 3.30.0. The `SoftReadWriteLock` site came with the `.rw` tree itself in f9743288a288 ("fix(soft-rw): replace the state mutex with a generation log", #738, 2026-09-17), first released in 4.0.0. Both match the git tags; the advisory range starts at the earlier one.
Mechanism
The owner of a directory can unlink and rename every entry in it, whatever the entries' own owner and mode. A strict hold consists of two names, `intent-v1-.claim` and `held-v1-.claim`, in `.filelock/claims`. The sentinel (the lock file at ``) is permanent and says nothing about who holds. When the other user runs the equivalent of `mkdir -m 777 -p work.lock.filelock/claims` before the lock user first uses `work.lock`, every lock user acquire publishes its claims into a directory the other …
Related
Alias · CVE (1)
- CVE-2026-112911 filelock is a platform-independent file lock for Python. vulnerability
Weakness · CWE (1)
- CWE-283 Unverified Ownership weakness
References
- https://github.com/tox-dev/filelock/security/advisories/GHSA-9c4x-gm8v-wgc4 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-112911 advisory
- https://github.com/tox-dev/filelock/pull/839 web
- https://github.com/tox-dev/filelock/commit/bfa066d65a1ba9106f86ee9d027f7588b9c77e03 web
- https://github.com/tox-dev/filelock package
- https://github.com/tox-dev/filelock/releases/tag/4.2.1 web