Skip to content

Commit 27d3e14

Browse files
committed
Create private SQLite database files
Pre-create new SQLite database files with mode 0600 on Unix before opening them with rusqlite. Existing database files are left unchanged. This requires database names to resolve to ordinary filesystem paths. SQLite's :memory: name and file: URI filenames are now rejected. This keeps persisted node and payment data from being readable by other local users when the database is created under a permissive umask. This commit was created with assistance from Codex.
1 parent 1ce07dc commit 27d3e14

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

src/io/sqlite_store/mod.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
use std::collections::HashMap;
1010
#[cfg(test)]
1111
use std::fs;
12+
#[cfg(unix)]
13+
use std::fs::OpenOptions;
1214
use std::future::Future;
15+
#[cfg(unix)]
16+
use std::os::unix::fs::OpenOptionsExt;
1317
use std::path::PathBuf;
1418
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
1519
use std::sync::{Arc, Mutex};
@@ -59,6 +63,8 @@ impl SqliteStore {
5963
/// If not already existing, a new SQLite database will be created in the given `data_dir` under the
6064
/// given `db_file_name` (or the default to [`DEFAULT_SQLITE_DB_FILE_NAME`] if set to `None`).
6165
///
66+
/// SQLite's `:memory:` database name and `file:` URI filenames are not supported.
67+
///
6268
/// Similarly, the given `kv_table_name` will be used or default to [`DEFAULT_KV_TABLE_NAME`].
6369
pub fn new(
6470
data_dir: PathBuf, db_file_name: Option<String>, kv_table_name: Option<String>,
@@ -233,6 +239,12 @@ impl SqliteStoreInner {
233239
data_dir: PathBuf, db_file_name: Option<String>, kv_table_name: Option<String>,
234240
) -> io::Result<Self> {
235241
let db_file_name = db_file_name.unwrap_or(DEFAULT_SQLITE_DB_FILE_NAME.to_string());
242+
if db_file_name == ":memory:" || db_file_name.starts_with("file:") {
243+
return Err(io::Error::new(
244+
io::ErrorKind::InvalidInput,
245+
"SQLite :memory: and file: database names are not supported",
246+
));
247+
}
236248
let kv_table_name = kv_table_name.unwrap_or(DEFAULT_KV_TABLE_NAME.to_string());
237249

238250
create_dir_all_private(&data_dir).map_err(|e| {
@@ -245,6 +257,16 @@ impl SqliteStoreInner {
245257
})?;
246258
let mut db_file_path = data_dir.clone();
247259
db_file_path.push(db_file_name);
260+
#[cfg(unix)]
261+
match OpenOptions::new().create_new(true).write(true).mode(0o600).open(&db_file_path) {
262+
Ok(_) => {},
263+
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {},
264+
Err(e) => {
265+
let msg =
266+
format!("Failed to create database file {}: {}", db_file_path.display(), e);
267+
return Err(io::Error::new(io::ErrorKind::Other, msg));
268+
},
269+
}
248270

249271
let mut connection = Connection::open(db_file_path.clone()).map_err(|e| {
250272
let msg =
@@ -701,6 +723,47 @@ mod tests {
701723
}
702724
}
703725

726+
#[cfg(unix)]
727+
#[test]
728+
fn creates_private_database_storage() {
729+
use std::os::unix::fs::PermissionsExt;
730+
731+
let mut data_dir = random_storage_path();
732+
data_dir.push("creates_private_database_storage");
733+
let db_file_name = "test_db";
734+
let db_file_path = data_dir.join(db_file_name);
735+
let _store = SqliteStore::new(
736+
data_dir.clone(),
737+
Some(db_file_name.to_string()),
738+
Some("test_table".to_string()),
739+
)
740+
.unwrap();
741+
742+
let dir_mode = data_dir.metadata().unwrap().permissions().mode();
743+
let file_mode = db_file_path.metadata().unwrap().permissions().mode();
744+
assert_eq!(dir_mode & 0o077, 0);
745+
assert_eq!(file_mode & 0o077, 0);
746+
}
747+
748+
#[test]
749+
fn rejects_sqlite_pseudo_filenames() {
750+
for db_file_name in [":memory:", "file:/tmp/node.db?mode=rwc"] {
751+
let data_dir = random_storage_path();
752+
let result = SqliteStore::new(
753+
data_dir.clone(),
754+
Some(db_file_name.to_string()),
755+
Some("test_table".to_string()),
756+
);
757+
let error = match result {
758+
Ok(_) => panic!("SQLite pseudo-filename was accepted"),
759+
Err(e) => e,
760+
};
761+
762+
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
763+
assert!(!data_dir.exists());
764+
}
765+
}
766+
704767
#[tokio::test]
705768
async fn read_write_remove_list_persist() {
706769
let mut temp_path = random_storage_path();

0 commit comments

Comments
 (0)