Repository navigation
🐛 fix(read-write): link private databases on Windows - #854
Merged
Merged
Conversation
WindowsFileLock reduced mode to a read-only attribute and created the lock file without a DACL of its own, so mode=0o600 gave the file whatever ACL its directory handed down, the same as no mode at all. The ReadWriteLock database, which filelock itself asks to be 0o600, had the same gap. A mode without group and other bits now creates the file with a protected DACL granting the owner, SYSTEM and Administrators full access, the ACL CPython applies for os.mkdir(mode=0o700). Other modes have no Windows counterpart and keep inheriting. As on Unix, a file that already exists keeps what it has. (cherry picked from commit 61a13d1)
On Windows ReadWriteLock handed SQLite the database path in every directory. SQLite also uses the -journal and -wal names beside it, so a file placed under one of those names by another account could fail every acquire or make SQLite replay a journal and delete the file it names. The lock now reads the directory's owner and ACL. When anyone besides the locking account, SYSTEM and Administrators may add or remove names there, SQLite opens a hard link inside a directory created with an owner-only ACL, as on POSIX. The link directory is pinned by an open handle while it is made, checked by file index, and removed with the same handle open. The sweep of leftover link directories uses the same checks. A filesystem without hard links raises OSError.
The concepts page and the how-to said Windows connects by name in every directory. Describe the ACL check and the link route, and keep the advice about directories for filesystems without hard links.
A directory whose ACL lets another account rewrite the ACL or take ownership was classified as private, though that account can then grant itself the right to add names. The check now counts WRITE_DAC and WRITE_OWNER with the name-creation rights.
gaborbernat
added a commit
that referenced
this pull request
Oct 17, 2026
This was referenced Oct 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
On Windows,
ReadWriteLockhanded SQLite the database path in every directory, so SQLite acted on-journaland-walfiles found beside it. It now chooses the route from the directory's access control list (ACL, the list of accounts allowed to act on it) and links the database privately where other accounts can add names, as the POSIX code from #834 does. This PR is based onfix/rw-sqlite-files(#834) and also carries one commit from #835, the owner-only security descriptor helper, until #835 merges.SQLite opened side files that another account could place beside the database
SQLite derives the
-journal,-waland-shmnames from the path it is given. Before the lock runs any statement, SQLite deletes a-walbeside an empty database and replays a rollback journal (its record of a transaction to undo) beside a non-empty one. Replaying a journal also deletes the super-journal file the journal names. In a directory where another account can create files, such asC:\Windows\TemporC:\Users\Public, that account can place such a file. A-walthe lock account cannot delete made every acquire fail withsqlite3.OperationalError: disk I/O error. A journal made SQLite write into the database and delete a file of the lock account. A read-only journal made every acquire fail withunable to open database file. The Windows branch of_connecthas connected by name since #399, and #834 left it unchanged._connectnow asks whether the directory is private._directory_is_privatereads the owner and the ACL withGetNamedSecurityInfoW. The directory is private when its owner, and every access-allowed entry that grantsFILE_ADD_FILE,FILE_ADD_SUBDIRECTORY,FILE_DELETE_CHILD,WRITE_DAC,WRITE_OWNER,GENERIC_WRITEorGENERIC_ALL, names the current account,SYSTEM,AdministratorsorCREATOR OWNER. Entries that only children inherit count, and an ACL the account cannot read counts as not private. In a private directory SQLite gets the database path. Otherwise the lock makes a.filelock-*directory with the owner-only descriptor from #835 and a random subdirectory inside it, hard links the database there, and gives SQLite the link. Nobody else can create a side file in a directory with that ACL.While the link is made, an open handle without delete sharing pins the directory's name against a rename, and the ACL is read again once the handle is open. The link's file index must match the database's. On release the lock opens the directory again, compares its file index with the one recorded at creation, removes the link, its side files and the subdirectory by full path while the handle pins the name, then removes the directory. The sweep of directories left by dead holders applies the same checks and parses a name only in a private directory. A filesystem without hard links raises
OSError, as on POSIX. The how-to keeps the advice to use a directory other accounts cannot write for such filesystems.flowchart TD A[acquire] --> B{only the account,<br/>SYSTEM, Administrators<br/>can add names?} B -- yes --> C[SQLite opens<br/>the database path] B -- "no, or ACL unreadable" --> D[owner-only .filelock-* directory<br/>pinned by an open handle] D --> E[hard link to the database<br/>file index checked] E --> F[SQLite opens the link] E -- "no hard links" --> G[OSError] classDef ok fill:#d8f3dc,stroke:#2d6a4f,color:#1b4332; classDef bad fill:#ffd6d6,stroke:#c1121f,color:#5c0000; classDef info fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a; class A,B info; class C,F ok; class G bad;Behavior changes
On Windows, a database in a directory that other accounts can write now opens through a link in a
.filelock-*directory beside it, and the filesystem must support hard links. A directory only the locking account,SYSTEMandAdministratorscan write is unchanged.This resolves GHSA-9vhv-jxww-xfm5.