Repository navigation
Refuse an RPC token wider than 0600 - #1081
Conversation
cargo-deny 0.20.2 reports error[wildcard] on every workspace = true line. There is still no version star in the tree. Co-authored-by: Cursor <cursoragent@cursor.com>
The node creates rpc.token owner-only, then accepted a group- or world-readable file on the next start. The mode is read from the same handle as the secret. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Disposition
One item is still open. The mode check is doing what it should, and the Linux test job is red because one unit test was not updated to match it.
auth::tests::resolve_reuses_existing_token still plants the token with fs::write and then expects resolve_rpc_auth to succeed. Under umask 022 that file is mode 0644, so the new check refuses it. Job https://github.com/reardencode/rbitcoin/actions/runs/39187126120/job/117851794861 failed there:
RPC token …/rpc.token: mode 644 is wider than 0600
That test is the reuse path (an operator-supplied token is kept, not regenerated). Chmod it to 0600 before resolve_rpc_auth, the same way the other planted tokens in this PR were fixed.
Same gap, smaller: empty_token_file_errors writes \n and only asserts is_err. On Unix that error is now the mode check, so the test no longer reaches the empty-token branch.
What holds
The check matches the cookie owner ceiling and it is on the opened handle, not a path stat done earlier:
File::open, thenmetadata()on that handle, then read. A replace of the path between the check and the read does not change which inode was accepted.- Unix mask is
mode & 0o777thenmode & 0o077 != 0. Group or world read, write, or execute refuses startup. That is the same owner rule as--rpc-cookie-perms owner. write_token_filestill usesOpenOptionsExt::mode(0o600)andcreate_new. Umask can only clear bits, so a file this process creates cannot be wider than 0600. An existing file is not chmodded.- Callers that plant a token and then start a node (
cross_surface,end_of_ibd,asmap_journey,integration_multinode, the RPC server test,write_rpc_token) chmod 0600 on Unix. The core-functionalbitcoindshim already chmods therpcpasswordseed to 0600 before the node starts.rbitcoin-clitests read the file themselves and do not callread_token_file. - Windows has no mode bits. The changelog, CLI help,
docs/rpc.md, and the operator table say so. - There is no
version = "*"in the workspace. CI pins cargo-deny 0.20.2 (.github/workflows/ci.yml). Withwildcards = "deny"that release reportserror[wildcard]on everyworkspace = truedependency. Leavingwildcards = "allow", with that reason indeny.toml, matches the tool we actually run.
Not blocking
These are the same class of gap as the cookie check. This PR does not make them worse.
- Owner execute and owner read-only (
0700,0400) pass. The mask is group and world, not "exactly 0600". The changelog line "must be mode 0600" is tighter than the code. The error string says "wider than 0600", which is the cookie wording for this mask.0700does not grant group or world read. - Setuid, setgid, and sticky are stripped by
& 0o777before the test, same as the cookie. - The check does not compare
uid. A root node will read another user's mode-0600 token. A non-root node cannot open that file. openfollows symlinks.fstatis the target. A symlink whose target is owner-only is accepted.O_NOFOLLOWis not used.- POSIX ACLs are visible only to the extent they show up in the mode bits. On Linux the ACL mask usually does. A filesystem that hides an extra grant from
st_modewould not be refused. - The parent directory mode is not checked. A world-writable datadir can still unlink the token. Replacing it with a group- or world-readable file now fails startup instead of loading a leaked bearer. Replacing it with another user's 0600 file fails the open unless the node is root.
write_token_filedoes notfstatthe handle it just created. A normal Unix umask cannot widenmode(0o600). A filesystem that ignored the create mode would serve that token until the next start, which would then refuse it.read_token_filestill trims. A trailing newline is accepted. The cookie refuses a line ending because mempool and bitcoin-cli disagree. The token has one reader. That split is unchanged.- There is no
--rpc-token-perms. Group read of the bearer is refused on purpose. Another local user already has the mode-0660 unix socket path (--rpc-socket). rbitcoin-clidoes not apply this check. A wide token file is already readable. This node will not start and accept it.
wildcards = "deny" stays blocked on the cargo-deny diagnostic above, not on a version = "*" somewhere in the tree. Collapsing the multiple-versions skip list is a noise_sv2 pin change, separate from this lint.
| .permissions() | ||
| .mode() | ||
| & 0o777; | ||
| if mode & 0o077 != 0 { |
There was a problem hiding this comment.
This refusal is right, and it is already failing CI. resolve_reuses_existing_token (about forty lines above, not in this diff) still does fs::write of fixed-token\n and then resolve_rpc_auth(...).unwrap(). Umask 022 makes that file 0644, so the unwrap panics with mode 644 is wider than 0600. Linux test job 117851794861. Chmod 0600 in that test before resolve. empty_token_file_errors has the same plant and only asserts is_err, so on Unix it now stops at this branch and no longer reaches the empty-token check.
fs: :write follows umask, so those tests hit the mode refusal before the behavior they name. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The two tests that create a token with a normal write now set the file to mode 0600 before they read it. A normal write was leaving the file group- and world-readable, so the new check stopped them before they did what they were written for. The reuse test keeps an existing token instead of minting a new one. The empty-file test now fails because the file is blank, and it checks that the error says so. |
Summary
{datadir}/rpc.tokenmust be mode 0600. The node still creates a missing token at 0600 and does not chmod a file it finds.cargo denywildcardsstaysallow. cargo-deny 0.20.2 treats everyworkspace = truedependency as a wildcard. The tree has noversion = "*".These two commits landed on
harden/cookie-and-auditafter #1074 merged, so they were not in that PR.Test plan
token_file_refuses_group_or_world_bits(0644 and 0640 refused, 0600 accepted)cargo deny checkwithwildcards = "deny"reproducederror[wildcard]onworkspace = true, then the setting was put backqc,test,windows,macos,coverage)Made with Cursor