Skip to content

Commit da73948

Browse files
committed
Create config and cache files with 0600 permissions.
1 parent bc9282d commit da73948

6 files changed

Lines changed: 67 additions & 91 deletions

File tree

cmd/soroban-cli/src/commands/snapshot/create.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@ async fn get_ledger_metadata_from_archive(
647647
}
648648

649649
fs::rename(&dl_path, &cache_path).map_err(Error::RenameDownloadFile)?;
650+
let _ = crate::config::locator::set_hardened_permissions(&cache_path);
650651

651652
print.clear_previous_line();
652653
print.globeln(format!("Downloaded ledger headers for ledger {ledger}"));
@@ -760,6 +761,7 @@ async fn cache_bucket(
760761
}
761762

762763
fs::rename(&dl_path, &cache_path).map_err(Error::RenameDownloadFile)?;
764+
let _ = crate::config::locator::set_hardened_permissions(&cache_path);
763765
}
764766
Ok(cache_path)
765767
}

cmd/soroban-cli/src/commands/tx/edit.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
env,
33
fs::{self},
4-
io::{stdin, Cursor, IsTerminal, Write},
4+
io::{stdin, Cursor, IsTerminal},
55
path::PathBuf,
66
process::{self},
77
};
@@ -82,21 +82,12 @@ fn tmp_file(contents: &str) -> Result<(TempDir, PathBuf), Error> {
8282
let path = temp_dir.path().join("edit.json");
8383

8484
#[cfg(unix)]
85-
let mut file = {
86-
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
85+
{
86+
use std::os::unix::fs::PermissionsExt;
8787
fs::set_permissions(temp_dir.path(), fs::Permissions::from_mode(0o700))?;
88-
fs::OpenOptions::new()
89-
.write(true)
90-
.create(true)
91-
.truncate(true)
92-
.mode(0o600)
93-
.open(&path)?
94-
};
95-
96-
#[cfg(not(unix))]
97-
let mut file = fs::File::create(&path)?;
98-
99-
file.write_all(contents.as_bytes())?;
88+
}
89+
90+
crate::config::locator::write_hardened_file(&path, contents.as_bytes())?;
10091

10192
Ok((temp_dir, path))
10293
}

cmd/soroban-cli/src/config/data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub fn write(action: Action, rpc_url: &Url) -> Result<ulid::Ulid, Error> {
6565
};
6666
let id = ulid::Ulid::new();
6767
let file = actions_dir()?.join(id.to_string()).with_extension("json");
68-
std::fs::write(file, serde_json::to_string(&data)?)?;
68+
crate::config::locator::write_hardened_file(&file, serde_json::to_string(&data)?.as_bytes())?;
6969
Ok(id)
7070
}
7171

@@ -82,7 +82,7 @@ pub fn write_spec(hash: &str, spec_entries: &[xdr::ScSpecEntry]) -> Result<(), E
8282
for entry in spec_entries {
8383
contents.extend(entry.to_xdr(xdr::Limits::none())?);
8484
}
85-
std::fs::write(file, contents)?;
85+
crate::config::locator::write_hardened_file(&file, &contents)?;
8686
Ok(())
8787
}
8888

cmd/soroban-cli/src/config/locator.rs

Lines changed: 36 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use serde::de::DeserializeOwned;
44
use std::{
55
ffi::OsStr,
66
fmt::Display,
7-
fs::{self, OpenOptions},
8-
io::{self, Write},
7+
fs, io,
98
path::{Path, PathBuf},
109
str::FromStr,
1110
};
@@ -483,32 +482,11 @@ impl Args {
483482
.insert(network_passphrase.into(), contract_id.to_string());
484483

485484
let content = serde_json::to_string(&data)?;
485+
write_hardened_file(&path, content.as_bytes())?;
486486

487487
#[cfg(unix)]
488-
{
489-
use std::io::Write as _;
490-
use std::os::unix::fs::OpenOptionsExt;
491-
let mut to_file = OpenOptions::new()
492-
.create(true)
493-
.truncate(true)
494-
.write(true)
495-
.mode(0o600)
496-
.open(&path)?;
497-
to_file.write_all(content.as_bytes())?;
498-
set_hardened_permissions(&path)?;
499-
if let Ok(root) = self.config_dir() {
500-
fix_config_permissions(root);
501-
}
502-
}
503-
504-
#[cfg(not(unix))]
505-
{
506-
let mut to_file = OpenOptions::new()
507-
.create(true)
508-
.truncate(true)
509-
.write(true)
510-
.open(path)?;
511-
to_file.write_all(content.as_bytes())?;
488+
if let Ok(root) = self.config_dir() {
489+
fix_config_permissions(root);
512490
}
513491

514492
Ok(())
@@ -524,17 +502,11 @@ impl Args {
524502
let content = fs::read_to_string(&path).unwrap_or_default();
525503
let mut data: alias::Data = serde_json::from_str(&content).unwrap_or_default();
526504

527-
let mut to_file = OpenOptions::new()
528-
.create(true)
529-
.truncate(true)
530-
.write(true)
531-
.open(path)?;
532-
533505
data.ids.remove::<str>(network_passphrase);
534506

535507
let content = serde_json::to_string(&data)?;
536-
537-
Ok(to_file.write_all(content.as_bytes())?)
508+
write_hardened_file(&path, content.as_bytes())?;
509+
Ok(())
538510
}
539511

540512
pub fn get_contract_id(
@@ -662,6 +634,30 @@ pub(crate) fn set_hardened_permissions(path: &Path) -> io::Result<()> {
662634
Ok(())
663635
}
664636

637+
/// Writes `contents` to `path`, creating the file with `0600` on Unix and
638+
/// tightening any pre-existing looser permissions afterwards. Falls back to
639+
/// `std::fs::write` on non-Unix platforms.
640+
pub(crate) fn write_hardened_file(path: &Path, contents: &[u8]) -> io::Result<()> {
641+
#[cfg(unix)]
642+
{
643+
use std::io::Write as _;
644+
use std::os::unix::fs::OpenOptionsExt;
645+
let mut file = std::fs::OpenOptions::new()
646+
.write(true)
647+
.create(true)
648+
.truncate(true)
649+
.mode(0o600)
650+
.open(path)?;
651+
file.write_all(contents)?;
652+
set_hardened_permissions(path)?;
653+
}
654+
655+
#[cfg(not(unix))]
656+
std::fs::write(path, contents)?;
657+
658+
Ok(())
659+
}
660+
665661
pub fn ensure_directory(dir: PathBuf) -> Result<PathBuf, Error> {
666662
let parent = dir.parent().ok_or(Error::HomeDirNotFound)?;
667663

@@ -757,41 +753,15 @@ impl KeyType {
757753
) -> Result<PathBuf, Error> {
758754
let filepath = ensure_directory(self.path(pwd, key))?;
759755
let data = toml::to_string(value).map_err(|_| Error::ConfigSerialization)?;
760-
#[cfg(unix)]
761-
{
762-
use std::io::Write as _;
763-
use std::os::unix::fs::OpenOptionsExt;
764-
let mut file = std::fs::OpenOptions::new()
765-
.write(true)
766-
.create(true)
767-
.truncate(true)
768-
.mode(0o600)
769-
.open(&filepath)
770-
.map_err(|error| Error::IdCreationFailed {
771-
filepath: filepath.clone(),
772-
error,
773-
})?;
774-
file.write_all(data.as_bytes())
775-
.map_err(|error| Error::IdCreationFailed {
776-
filepath: filepath.clone(),
777-
error,
778-
})?;
779-
}
780-
781-
#[cfg(not(unix))]
782-
std::fs::write(&filepath, data).map_err(|error| Error::IdCreationFailed {
783-
filepath: filepath.clone(),
784-
error,
756+
write_hardened_file(&filepath, data.as_bytes()).map_err(|error| {
757+
Error::IdCreationFailed {
758+
filepath: filepath.clone(),
759+
error,
760+
}
785761
})?;
786762

787763
#[cfg(unix)]
788-
{
789-
set_hardened_permissions(&filepath).map_err(|error| Error::IdCreationFailed {
790-
filepath: filepath.clone(),
791-
error,
792-
})?;
793-
fix_config_permissions(pwd.to_path_buf());
794-
}
764+
fix_config_permissions(pwd.to_path_buf());
795765

796766
Ok(filepath)
797767
}

cmd/soroban-cli/src/config/mod.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
use serde::{Deserialize, Serialize};
2-
use std::{
3-
fs::{self, File},
4-
io::Write,
5-
};
2+
use std::fs;
63

74
use crate::{
85
commands::HEADING_TRANSACTION,
@@ -294,9 +291,8 @@ impl Config {
294291

295292
pub fn save_to(&self, path: &std::path::Path) -> Result<(), locator::Error> {
296293
let toml_string = toml::to_string(&self)?;
297-
// Depending on the platform, this function may fail if the full directory path does not exist
298-
let mut file = File::create(locator::ensure_directory(path.to_path_buf())?)?;
299-
file.write_all(toml_string.as_bytes())?;
294+
let path = locator::ensure_directory(path.to_path_buf())?;
295+
locator::write_hardened_file(&path, toml_string.as_bytes())?;
300296
Ok(())
301297
}
302298
}

cmd/soroban-cli/src/config/upgrade_check.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl UpgradeCheck {
6161

6262
let path = locator::ensure_directory(path)?;
6363
let data = serde_json::to_string(self).map_err(|_| locator::Error::ConfigSerialization)?;
64-
fs::write(&path, data)
64+
locator::write_hardened_file(&path, data.as_bytes())
6565
.map_err(|error| locator::Error::UpgradeCheckWriteFailed { path, error })
6666
}
6767
}
@@ -100,4 +100,21 @@ mod tests {
100100
let loaded_check = UpgradeCheck::load().unwrap();
101101
assert_eq!(loaded_check, saved_check);
102102
}
103+
104+
#[cfg(unix)]
105+
#[test]
106+
#[serial]
107+
fn test_upgrade_check_save_uses_0600_permissions() {
108+
use crate::test_utils::with_env_set;
109+
use std::os::unix::fs::PermissionsExt;
110+
111+
let temp_dir = tempfile::tempdir().unwrap();
112+
with_env_set("STELLAR_DATA_HOME", temp_dir.path(), || {
113+
UpgradeCheck::default().save().unwrap();
114+
115+
let path = project_dir().unwrap().data_dir().join(FILE_NAME);
116+
let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777;
117+
assert_eq!(mode, 0o600, "expected 0600, got {mode:o}");
118+
});
119+
}
103120
}

0 commit comments

Comments
 (0)