GitHub Advisoryadvisory · PyPI

GHSA-rfpc-qcw5-3pc4 filelock: SoftFileLock removes a live holder's marker when another user forges the PID in a writable marker

MEDIUM Published 2026-10-28 Modified 2026-10-28 Official page ↗
Aliases
CVE-2026-112939
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:N
Weaknesses
CWE-345
Reviewed
2026-10-28

Affected packages

• PyPI filelock: >= 3.22.0, < 4.2.1 → fixed in 4.2.1

Description

`SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async variants and a `FileLock` that has fallen back to a soft lock on `ENOSYS` decide from the record inside the marker file whether its owner is gone. The marker is the file whose presence says a process holds the lock. They do not check who could have written that record. A local user who can write the marker but cannot remove it rewrites the holder's record to name a dead PID (process id). That position arises with an explicit `mode` with group or other write bits, or the default mode under a lax umask (the process setting that strips permission bits from new files), in a sticky or otherwise non-removable directory. The sticky bit is a directory flag that lets only a file's owner remove or rename it. The next contender running as the holder's account, or root, judges the lock stale, removes the live marker on that user's behalf and acquires it. Two processes then run the critical section together and neither gets an error. Affected versions are filelock 3.22.0 through 4.2.0 (finding `ANT-2026-W1H9NXUH`). Severity CVSS 3.1 `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N` scores 4.7 (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 marker must carry group or other write bits (an explicit permissive `mode`, or umask `002`/`000` with a shared group), and a contender of the holder's account must ask for the lock while the forged record is in place. `I:H` (high integrity impact): two holders then write whatever the lock protects, and the holder is not told. `C:N` and `A:N` (no confidentiality or availability impact). `S:U` (scope unchanged). Classification from the review: security, conditional on another user being able to write the live marker. The scanner rated it high (CWE-345). The default mode under umask `022` is not affected. Scope and preconditions • The other user can write the marker. The application passes a `mode` with group or other write bits, which `fchmod` applies as given since 4.1.0. `0o666` is a common choice for a lock shared between accounts, although a soft-lock contender of another account waits as well on a `0o644` marker, since contenders only read and rename. Or the application uses the default mode (`0o666` through the umask) under umask `002` in a directory whose group the other user belongs to, or under umask `000`. The default mode under umask `022` is not affected. • The other user cannot remove the marker itself: the directory is sticky, or the other user cannot write it at all (a `0755` directory owned by the lock's user gives the same overlap). In a non-sticky shared directory the other user can unlink the marker without filelock, and filelock adds nothing. • A contender that may rename the marker asks for the lock: another process of the holder's account (several workers of one user) or root. A contender of another unprivileged account cannot rename the marker in a sticky directory and times out. • The finding reaches every class that publishes a soft marker: `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async variants, and `FileLock(mode=0o666)` on a filesystem where `flock` (a kernel lock on an open file) fails with `ENOSYS`. 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: the create mode in `SoftFileLock._acquire`, the explicit-mode `fchmod` in `_apply_explicit_mode`, the trusting verdict in `_try_break_stale_lock` and the `owner_is_stale` it relies on; the same call in `MarkerSoftFileLock` and `SoftFileLease`. Introduced The stale verdict that trusts the record came in with e35c3afac473 ("feat(soft): detect and break stale locks", #476, 2026-02-13), first released in 3.22.0. From then on a marker left group- or other-writable by the umask is forgeable. The reproducer's exact configuration, an explicit `mode=0o666` under umask `022`, needs 66d309d ("fix(soft): honour mode and tighten marker grammar", #807), first released in 4.1.0. Both match the git tags. Mechanism A contender whose `O_CREAT | O_EXCL` create fails with `EEXIST` goes to `_try_break_stale_lock`. `_read_lock_file` makes sure the path is a regular file (`lstat`, `O_NOFOLLOW` open, `fstat`) and returns its content, mtime and inode (the kernel's identity for a file, independent of its name). It has the `fstat` result in hand but does not look at `st_uid` or `st_mode`. The code parses the content into `pid`, `hostname` and start token and hands them to `owner_is_stale`, whose docstring promises to return `True` "only when this process can prove the exact recorded owner is dead". That proof holds only if the owner wrote the record. A process of another account that can open the marker for writing uses `O_WRONLY | O_TRUNC` (no `O_CREAT`, so `fs.protected_regular` does not block it) and writes `2147483647\n\n`, keeping the host line. The host matches, PID 2147483647 does not exist, and `owner_is_stale` returns `True`. `break_lock_file` renames the marker to `.break..` and unlinks it. Its mtime and inode guard compares against what the contender read after the rewrite, so it passes. The contender, which runs as the marker's owner, does the rename, so the sticky bit allows it. On the next poll the contender's `O_EXCL` create succeeds. The first holder is not told: `SoftFileLock` has no heartbeat, and on release `_unlink_open_marker` finds another inode or no file at the path and returns without an error. Other records lead to the same break, each run by the scanner the same way: the holder's own PID and host with a different third line (`owner_is_stale` reads a live PID with another start token as a recycled PID); an empty file (malformed, broken after `MALFORMED_LOCK_AGE_THRESHOLD`, 2 s). `MarkerSoftFileLock` and `SoftFileLease` apply `owner_is_stale` to the `pid=`, `host=` and `start=` fields of a protocol 2 record, and rewriting `pid=` admitted a second process for both. A `SoftFileLease` holder learns of it at its next heartbeat through `on_compromise`; `SoftFileLock` and `MarkerSoftFileLock` holders do not learn of it at all. The marker becomes writable by another account in two ways. An explicit `mode` with group or other write bits goes through `fchmod`, so the umask does not narrow it (`test_lock_mode_soft` at the scanned commit pins `-rw-rw-rw-` under umask `022`). The default mode passes `0o666` to `os.open`, so under umask `002` the marker is `0o664`. With no `mode` argument, in a setgid and sticky group directory (`3770`), a second member of the group rewrote the marker and a second process of the holder's user acquired the same way. Under umask `000` the marker is `0o666` and any local user can rewrite it. `FileLock(mode=0o666)` on a filesystem where `flock` fails with `ENOSYS` falls back to `SoftFileLock` and produ…

Related

Alias · CVE (1)

  • CVE-2026-112939 filelock is a platform-independent file lock for Python. vulnerability

Weakness · CWE (1)

  • CWE-345 Insufficient Verification of Data Authenticity weakness

References