Skip to content

Commit 0abcdd5

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 0abcdd5

1 file changed

Lines changed: 71 additions & 2 deletions

File tree

src/io/sqlite_store/mod.rs

Lines changed: 71 additions & 2 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,17 @@ 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+
let mut db_file_path = data_dir.clone();
243+
db_file_path.push(&db_file_name);
244+
if db_file_name == ":memory:"
245+
|| db_file_name.starts_with("file:")
246+
|| db_file_path.to_string_lossy().starts_with("file:")
247+
{
248+
return Err(io::Error::new(
249+
io::ErrorKind::InvalidInput,
250+
"SQLite :memory: and file: database names are not supported",
251+
));
252+
}
236253
let kv_table_name = kv_table_name.unwrap_or(DEFAULT_KV_TABLE_NAME.to_string());
237254

238255
create_dir_all_private(&data_dir).map_err(|e| {
@@ -243,8 +260,16 @@ impl SqliteStoreInner {
243260
);
244261
io::Error::new(io::ErrorKind::Other, msg)
245262
})?;
246-
let mut db_file_path = data_dir.clone();
247-
db_file_path.push(db_file_name);
263+
#[cfg(unix)]
264+
match OpenOptions::new().create_new(true).write(true).mode(0o600).open(&db_file_path) {
265+
Ok(_) => {},
266+
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {},
267+
Err(e) => {
268+
let msg =
269+
format!("Failed to create database file {}: {}", db_file_path.display(), e);
270+
return Err(io::Error::new(io::ErrorKind::Other, msg));
271+
},
272+
}
248273

249274
let mut connection = Connection::open(db_file_path.clone()).map_err(|e| {
250275
let msg =
@@ -701,6 +726,50 @@ mod tests {
701726
}
702727
}
703728

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

0 commit comments

Comments
 (0)