{"id":"GHSA-j8f7-rjxc-mr56","summary":"filelock: ReadWriteLock follows a symlink at the lock path into SQLite","details":"### Summary\n\n`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.\n\n### Details\n\n`ReadWriteLock.__init__` stores `pathlib.Path(lock_file).resolve()`, which follows a final symlink, and every connection opens that path through `sqlite3.connect()` with no `O_NOFOLLOW` or regular-file check. SQLite's own VFS also resolves the final symlink in user space, so `fs.protected_symlinks` and the sticky bit do not help: the kernel never sees a symlink to refuse. The other lock classes do not follow a final symlink: `FileLock` opens with `O_NOFOLLOW`, and `SoftReadWriteLock` coordinates through `\u003cpath\u003e.rw/`.\n\n### Proof of concept\n\nOn Linux, with the victim and attacker as different users and `/shared` mode 1777 with `fs.protected_symlinks=1`:\n\n1. The attacker runs `ln -s /home/victim/private/pwned.flag /shared/app.rwlock.db`.\n2. The victim constructs `ReadWriteLock(\"/shared/app.rwlock.db\")`. A plain `os.open(..., O_NOFOLLOW)` on the same path fails, but the lock creates `/home/victim/private/pwned.flag`, owned by the victim, inside a directory the attacker cannot write.\n3. With `/shared/cache.rwlock.db -\u003e /shared/a.db`, one victim process holds the write lock. The attacker repoints the link to `/shared/b.db`, and a second victim process acquires the write lock while the first still holds it.\n\nThe same behavior reproduces on macOS, on Windows 11, and through `AsyncReadWriteLock`.\n\n### Impact\n\nA local attacker with write access to the lock directory can make the victim create empty files it owns at paths the attacker cannot write, make the victim hold a SQLite lock on another application's database so that application gets `database is locked`, and give two victim processes the write lock at once, letting them corrupt the resource the lock protects.\n\n### Workarounds\n\nKeep `ReadWriteLock` paths in directories that only trusted users can write.","aliases":["CVE-2026-112936"],"modified":"2026-10-28T21:15:04.169983430Z","published":"2026-10-28T21:08:30Z","database_specific":{"nvd_published_at":"2026-10-20T15:17:16Z","cwe_ids":["CWE-59","CWE-668"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-28T21:08:30Z"},"references":[{"type":"WEB","url":"https://github.com/tox-dev/filelock/security/advisories/GHSA-j8f7-rjxc-mr56"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112936"},{"type":"WEB","url":"https://github.com/tox-dev/filelock/commit/4d45e83caf7e31f9a12fb44526bdc07f70114f04"},{"type":"PACKAGE","url":"https://github.com/tox-dev/filelock"},{"type":"WEB","url":"https://github.com/tox-dev/filelock/releases/tag/4.0.9"}],"affected":[{"package":{"name":"filelock","ecosystem":"PyPI","purl":"pkg:pypi/filelock"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.20.4"},{"fixed":"4.0.9"}]}],"versions":["3.20.4","3.21.0","3.21.1","3.21.2","3.22.0","3.23.0","3.24.0","3.24.1","3.24.2","3.24.3","3.25.0","3.25.1","3.25.2","3.28.0","3.29.0","3.29.1","3.29.2","3.29.3","3.29.4","3.29.5","3.29.6","3.29.7","3.30.0","3.30.1","3.30.2","3.30.3","3.31.0","3.31.1","3.31.2","3.32.0","3.32.2","3.32.3","3.32.4","3.32.5","3.32.6","3.32.7","4.0.0","4.0.1","4.0.2","4.0.3","4.0.4","4.0.5","4.0.6","4.0.7","4.0.8"],"database_specific":{"last_known_affected_version_range":"\u003c= 4.0.8","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-j8f7-rjxc-mr56/GHSA-j8f7-rjxc-mr56.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L"}]}