filelock on Windows ignores the mode argument. FileLock(path, mode=0o600) creates the same file as FileLock(path): one with no security descriptor of its own (the Windows record that says who may access a file). Its access list, or ACL, is whatever the lock directory hands down to new files. filelock creates the ReadWriteLock database, which it asks to be 0o600, the same way. In a directory whose inheritable ACL grants other …
UnixFileLock, the backend of FileLock and AsyncFileLock on Unix, empties whatever regular file it finds at the lock path once it holds the flock (a kernel lock on an open file). With an explicit mode it also chmods that file (sets its permission bits), whether or not this acquire created it. Another local user who can rename or hard-link one of the files of the user running the lock (below: …
raise_on_not_writable_file() is the check that SoftFileLock and the locks sharing its acquire path run before every attempt. It looks at the lock path with os.lstat, so that it judges a planted symlink (a file that points at another path) by itself. When the node has no owner-write bit, it then calls os.access(filename, os.W_OK), which follows symlinks. A local user who can create entries in the lock's directory can swap the …
StrictSoftFileLock and AsyncStrictSoftFileLock check <path>.filelock (the coordination directory that holds the lock's state) with one lstat call on its name. lstat reports what a name is without following a symbolic link (a file that redirects to another path). After that check, the code reaches <path>.filelock/claims by pathname for every later operation (mkdir, scan, publish, link, unlink). _open_directory passes O_NOFOLLOW (a flag that makes an open fail when the final name …
StrictSoftFileLock and AsyncStrictSoftFileLock apply the caller's mode to the sentinel (the lock file at <path>) and to both claim files (the files whose presence says a process wants or holds the lock). They create the two directories that hold the claims, <path>.filelock and <path>.filelock/claims, with a bare mkdir(). That gives 0o777 filtered through the umask (a per-process mask that removes permission bits from new files and directories) or, under a …
StrictSoftFileLock, AsyncStrictSoftFileLock, SoftReadWriteLock and AsyncSoftReadWriteLock keep their whole ownership state as files in a directory derived from the lock path (<path>.filelock/claims, <path>.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 …
SoftReadWriteLock (and AsyncSoftReadWriteLock, which wraps one) refreshes its holder record (the file under <path>.rw/holders/ that says a process holds or wants the lock) on every poll of a waiting acquire and on every heartbeat (the periodic refresh a holder runs to keep its record fresh). It does so by reopening <path>.rw/holders/<token> by name with a plain blocking O_WRONLY | O_NOFOLLOW open and writing at offset 0, without checking what it …
SoftReadWriteLock (and AsyncSoftReadWriteLock, which wraps one) checks once per lock object, by name, that <path>.rw, <path>.rw/gen and <path>.rw/holders are real directories and not symbolic links (files that redirect to another path). It then keeps only a boolean. Every later record operation resolves the full path again, and O_NOFOLLOW (a flag that makes an open fail when the final name is a symbolic link) protects the last component only. Another local …
The stale-lock break shared by SoftFileLock, MarkerSoftFileLock and SoftFileLease (and their async variants, and a FileLock that has fallen back to a soft lock on ENOSYS, the "not implemented" error) recognises the marker it judged stale by two values. The marker is the lock file at <path>, whose presence says a process holds the lock. The values are its inode number (the kernel's identity for a file, independent of its …
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 …
ReadWriteLock and AsyncReadWriteLock resolve the lock path with pathlib.Path.resolve() and pass it to sqlite3.connect() without refusing a symlink. A local user who can create names in the lock's directory, such as /tmp or a shared cache, can plant a symlink at the lock path and make the victim's process create, lock, or write the file it points to, or break mutual exclusion between the victim's own processes.
ReadWriteLock and AsyncReadWriteLock (the SQLite-backed reader/writer lock) run PRAGMA journal_mode=MEMORY on a fresh connection for every acquire. That statement makes SQLite load the whole schema (the sqlite_master table, which lists every table and index with its CREATE text) of whatever file sits at the lock path. The caller's timeout reaches SQLite only as busy_timeout, which bounds waits for another connection's lock, not schema loading. The only content checks reject non-regular …
When a soft-lock stale break finds that the marker (the lock file at <path>) it renamed aside was a live one recreated by a successor, the restore branch of break_lock_file() checks the break name with lstat (a check that reads a name without following a symbolic link) and then calls os.link(break_path, lock_file) by name. On CPython 3.12 and later on Linux (and on macOS) that call follows a symbolic link …
SoftFileLock and MarkerSoftFileLock evict an unparsable marker (the lock file at <path>, whose presence says a process holds the lock) once a waiting contender has watched it unchanged for two seconds. SoftFileLease expires a lease the same way. The watch tells two looks apart by the marker's modification time (mtime) and inode number (the kernel's identity for a file, independent of its name) alone. Another local user (the other user …
On Windows, ReadWriteLock and AsyncReadWriteLock (the SQLite-backed reader/writer lock) connect SQLite to the lock database by its path, in every directory. SQLite derives three side files from that path: <database>-journal, the rollback journal (pages saved to undo an interrupted write); <database>-wal, the write-ahead log (changes kept outside the main file); and <database>-shm, the shared-memory index of the WAL. SQLite acts on whichever of these it finds beside the database before …
ReadWriteLock and AsyncReadWriteLock (the SQLite-backed reader/writer lock) remove their private .filelock-* link directories with shutil.rmtree (a recursive delete) on a remembered path. They do not check that the path still names the directory they created. This happens on the hard-link route (a hard link is a second name for the same file). That route is every connection on macOS since 4.1.1 and any POSIX process without a usable /proc/self/fd (a …
On Windows the soft locks (SoftFileLock, MarkerSoftFileLock, SoftFileLease, their async forms, and a FileLock that fell back to a soft lock) read their marker by path. The marker is the lock file whose presence says a process holds the lock. Each acquire() attempt that finds a marker opens it with os.open to read the holder's process id, and the lock renames and deletes a marker it judges stale, by path. …
ReadWriteLock and AsyncReadWriteLock (the SQLite-backed reader/writer lock) hand SQLite a name they do not control. On Linux the lock validates its database through a descriptor (the number the kernel gives a process for an open file). It then connects SQLite to /proc/self/fd/<n>, a Linux path that names the process's own open file n. SQLite turns that path back into a plain path and reopens it by name, following symlinks (a …
Title: Time-of-Check-Time-of-Use (TOCTOU) Symlink Vulnerability in SoftFileLock Affected Component: filelock package - SoftFileLock class File: src/filelock/_soft.py lines 17-27 CWE: CWE-362, CWE-367, CWE-59