-
Notifications
You must be signed in to change notification settings - Fork 35
fix: create Sqlite databases with owner-only permissions (CWE-732) #2114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,57 @@ use rusqlite::{Connection, OpenFlags, OptionalExtension}; | |||||||||||||||||||||||||||
| use error::process_sqlite_error; | ||||||||||||||||||||||||||||
| use storage_core::{Data, DbDesc, DbMapId, backend}; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// The database can contain highly sensitive data (wallet databases store private keys and | ||||||||||||||||||||||||||||
| /// optionally the seed phrase), so it must never be readable by other users. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// If the directory does not exist, it is created with owner-only permissions (0700), which | ||||||||||||||||||||||||||||
| /// also protects the auxiliary files that Sqlite creates (rollback journal, WAL, | ||||||||||||||||||||||||||||
| /// shared-memory, temporary files). The permissions of pre-existing directories are left | ||||||||||||||||||||||||||||
| /// untouched, since they may be shared with unrelated data. | ||||||||||||||||||||||||||||
| #[cfg(unix)] | ||||||||||||||||||||||||||||
| fn ensure_private_directory(dir: &Path) -> std::io::Result<()> { | ||||||||||||||||||||||||||||
| use std::os::unix::fs::PermissionsExt; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Determine whether this call creates the directory by attempting an atomic create_dir | ||||||||||||||||||||||||||||
| // first, instead of a racy exists() check: if the leaf already exists its permissions | ||||||||||||||||||||||||||||
| // are deliberately left untouched (it may be shared with unrelated data), otherwise it | ||||||||||||||||||||||||||||
| // was created by this call and is immediately tightened to 0700. | ||||||||||||||||||||||||||||
| let created = match std::fs::create_dir(dir) { | ||||||||||||||||||||||||||||
| Ok(()) => true, | ||||||||||||||||||||||||||||
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => { | ||||||||||||||||||||||||||||
| // Some parent component was missing; create the whole path. The leaf did not | ||||||||||||||||||||||||||||
| // exist in this case either, so it was created by this call as well. | ||||||||||||||||||||||||||||
| std::fs::create_dir_all(dir)?; | ||||||||||||||||||||||||||||
| true | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => false, | ||||||||||||||||||||||||||||
| Err(err) => return Err(err), | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if created { | ||||||||||||||||||||||||||||
| std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))?; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Create the database file atomically with owner-only permissions (0600), so that its | ||||||||||||||||||||||||||||
| /// (temporarily empty) contents are never observable by other users. If the file already | ||||||||||||||||||||||||||||
| /// exists, its permissions are repaired to 0600 instead. Returns whether the file was created. | ||||||||||||||||||||||||||||
| #[cfg(unix)] | ||||||||||||||||||||||||||||
| fn create_private_file(path: &Path) -> std::io::Result<bool> { | ||||||||||||||||||||||||||||
| use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| match std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(path) { | ||||||||||||||||||||||||||||
| Ok(_file) => Ok(true), | ||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||
| Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { | ||||||||||||||||||||||||||||
| std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?; | ||||||||||||||||||||||||||||
| Ok(false) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||
| Err(err) => Err(err), | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| use crate::queries::SqliteQueries; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Note: DbTx holds the mutex itself and locks it on every operation instead of just holding a lock | ||||||||||||||||||||||||||||
|
|
@@ -441,6 +492,9 @@ impl backend::Backend for Sqlite { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if let SqliteStorageMode::File(ref path) = self.backend { | ||||||||||||||||||||||||||||
| if let Some(parent) = path.parent() { | ||||||||||||||||||||||||||||
| #[cfg(unix)] | ||||||||||||||||||||||||||||
| ensure_private_directory(parent).map_err(error::process_io_error)?; | ||||||||||||||||||||||||||||
| #[cfg(not(unix))] | ||||||||||||||||||||||||||||
| std::fs::create_dir_all(parent).map_err(error::process_io_error)?; | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| return Err(storage_core::error::Fatal::Io( | ||||||||||||||||||||||||||||
|
|
@@ -449,6 +503,11 @@ impl backend::Backend for Sqlite { | |||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| .into()); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Pre-create the database file with owner-only permissions so that Sqlite never | ||||||||||||||||||||||||||||
| // creates it with the default (world-readable) permissions. | ||||||||||||||||||||||||||||
| #[cfg(unix)] | ||||||||||||||||||||||||||||
| create_private_file(path).map_err(error::process_io_error)?; | ||||||||||||||||||||||||||||
|
Comment on lines
+509
to
+510
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let queries = desc.db_maps().transform(queries::SqliteQuery::from_desc); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When intermediate parent components are missing,
create_dir_allcreates them with the process umask (potentially group/world-readable), and only the leaf is tightened to 0700. Sensitive intermediate directories on the wallet path (e.g. ~/.wallet//) can remain readable by others. Additionally there is a small TOCTOU window between directory creation andset_permissions(0700)during which the new leaf has umask permissions. Consider creating components one at a time withcreate_dir+ immediateset_permissions, or tightening every component this call created.Suggestion: