Skip to content

Commit d0ba0d9

Browse files
authored
fix: race condition in private key creation (#4673)
1 parent b9c5d42 commit d0ba0d9

File tree

6 files changed

+39
-53
lines changed

6 files changed

+39
-53
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ jsonrpsee = { version = "0.24", features = ["server", "ws-client", "http-client"
8888
jsonwebtoken = "9"
8989
keccak-hash = "0.10"
9090
kubert-prometheus-process = "0.1"
91-
libc = "0.2"
9291
libipld = { version = "0.16", default-features = false, features = ["dag-cbor", "dag-json", "derive", "serde-codec"] }
9392
libipld-core = { version = "0.16", features = ['arb', 'serde-codec'] }
9493
libipld-macro = "0.16"

src/key_management/keystore.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
use std::{
55
fmt::Display,
6-
fs::{self, create_dir, File},
6+
fs::{create_dir, File},
77
io::{BufReader, BufWriter, ErrorKind, Read, Write},
88
path::{Path, PathBuf},
99
};
1010

11-
use crate::{shim::crypto::SignatureType, utils::encoding::from_slice_with_fallback};
11+
use crate::{
12+
shim::crypto::SignatureType,
13+
utils::{encoding::from_slice_with_fallback, io::create_new_sensitive_file},
14+
};
1215
use ahash::{HashMap, HashMapExt};
1316
use argon2::{
1417
password_hash::SaltString, Argon2, ParamsBuilder, PasswordHasher, RECOMMENDED_SALT_LEN,
@@ -269,16 +272,7 @@ impl KeyStore {
269272
pub fn flush(&self) -> anyhow::Result<()> {
270273
match &self.persistence {
271274
Some(persistent_keystore) => {
272-
let dir = persistent_keystore
273-
.file_path
274-
.parent()
275-
.ok_or_else(|| Error::Other("Invalid Path".to_string()))?;
276-
fs::create_dir_all(dir)?;
277-
let file = File::create(&persistent_keystore.file_path)?;
278-
279-
// Restrict permissions on files containing private keys
280-
#[cfg(unix)]
281-
crate::utils::io::set_user_perm(&file)?;
275+
let file = create_new_sensitive_file(&persistent_keystore.file_path)?;
282276

283277
let mut writer = BufWriter::new(file);
284278

src/libp2p/keypair.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use tracing::{debug, info, trace};
55

6-
use crate::{libp2p::Keypair, utils::io::write_to_file};
6+
use crate::{libp2p::Keypair, utils::io::write_new_sensitive_file};
77
use std::{fs, path::Path};
88

99
const KEYPAIR_FILE: &str = "keypair";
@@ -29,12 +29,10 @@ fn create_and_save_keypair(path: &Path) -> anyhow::Result<Keypair> {
2929
backup_path.set_extension("bak");
3030

3131
info!("Backing up existing keypair to {}", backup_path.display());
32-
fs::rename(keypair_path, &backup_path)?;
32+
fs::rename(&keypair_path, &backup_path)?;
3333
}
3434

35-
let file = write_to_file(&gen_keypair.to_bytes(), path, KEYPAIR_FILE)?;
36-
// Restrict permissions on files containing private keys
37-
crate::utils::io::set_user_perm(&file)?;
35+
write_new_sensitive_file(&gen_keypair.to_bytes(), &keypair_path)?;
3836

3937
Ok(gen_keypair.into())
4038
}

src/utils/io/mod.rs

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,41 @@ pub mod progress_log;
66
mod writer_checksum;
77

88
use std::{
9-
fs::{create_dir_all, File},
10-
io::{prelude::*, Result},
9+
fs::File,
10+
io::{self, prelude::*, Result},
11+
os::unix::fs::OpenOptionsExt,
1112
path::Path,
1213
};
1314

1415
pub use mmap::EitherMmapOrRandomAccessFile;
1516
pub use progress_log::{WithProgress, WithProgressRaw};
1617
pub use writer_checksum::*;
1718

18-
/// Restricts permissions on a file to user-only: 0600
19-
#[cfg(unix)]
20-
pub fn set_user_perm(file: &File) -> Result<()> {
21-
use std::os::unix::fs::PermissionsExt;
22-
23-
use tracing::info;
24-
25-
let mut perm = file.metadata()?.permissions();
26-
#[allow(clippy::useless_conversion)] // Otherwise it does not build on macos
27-
perm.set_mode((libc::S_IWUSR | libc::S_IRUSR).into());
28-
file.set_permissions(perm)?;
29-
30-
info!("Permissions set to 0600 on {:?}", file);
31-
32-
Ok(())
19+
/// Writes bytes to a specified file. Creates the desired path if it does not
20+
/// exist.
21+
/// Note: The file is created with permissions 0600.
22+
/// Note: The file is truncated if it already exists.
23+
pub fn write_new_sensitive_file(message: &[u8], path: &Path) -> Result<()> {
24+
create_new_sensitive_file(path)?.write_all(message)
3325
}
3426

35-
#[cfg(not(unix))]
36-
pub fn set_user_perm(file: &File) -> Result<()> {
37-
Ok(())
38-
}
27+
/// Creates a new file with the specified path. The file is created
28+
/// with permissions 0600 and is truncated if it already exists.
29+
pub fn create_new_sensitive_file(path: &Path) -> Result<File> {
30+
std::fs::create_dir_all(
31+
path.parent()
32+
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Parent directory not found"))?,
33+
)?;
3934

40-
/// Writes a string to a specified file. Creates the desired path if it does not
41-
/// exist. Note: `path` and `filename` are appended to produce the resulting
42-
/// file path.
43-
pub fn write_to_file(message: &[u8], path: &Path, file_name: &str) -> Result<File> {
44-
// Create path if it doesn't exist
45-
create_dir_all(path)?;
46-
let mut file = File::create(path.join(file_name))?;
47-
file.write_all(message)?;
35+
let file = std::fs::OpenOptions::new()
36+
.mode(0o600)
37+
.write(true)
38+
.create(true)
39+
.truncate(true)
40+
.open(path)?;
41+
42+
use std::os::unix::fs::PermissionsExt;
43+
file.set_permissions(std::fs::Permissions::from_mode(0o600))?;
4844
Ok(file)
4945
}
5046

src/utils/tests/files.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
path::{Path, PathBuf},
77
};
88

9-
use crate::utils::io::{read_toml, write_to_file};
9+
use crate::utils::io::{read_toml, write_new_sensitive_file};
1010
use serde::Deserialize;
1111

1212
// Please use with caution, remove_dir_all will completely delete a directory
@@ -20,7 +20,7 @@ fn write_to_file_to_path() {
2020
let path = PathBuf::from("./test-write");
2121
let file = "test.txt";
2222

23-
match write_to_file(msg, &path, file) {
23+
match write_new_sensitive_file(msg, &path.join(file)) {
2424
Ok(_) => cleanup_file(&path),
2525
Err(e) => {
2626
cleanup_file(&path);
@@ -34,7 +34,7 @@ fn write_to_file_nested_dir() {
3434
let msg = "Hello World!".as_bytes();
3535
let root = PathBuf::from("./test_missing");
3636

37-
match write_to_file(msg, &root.join("test_write_string"), "test-file") {
37+
match write_new_sensitive_file(msg, &root.join("test_write_string").join("test-file")) {
3838
Ok(_) => cleanup_file(&root),
3939
Err(e) => {
4040
cleanup_file(&root);
@@ -48,7 +48,7 @@ fn read_from_file_vec() {
4848
let msg = "Hello World!".as_bytes();
4949
let path = PathBuf::from("./test_read_file");
5050
let file_name = "out.keystore";
51-
write_to_file(msg, &path, file_name).unwrap();
51+
write_new_sensitive_file(msg, &path.join(file_name)).unwrap();
5252

5353
match std::fs::read(path.join(file_name)) {
5454
Ok(contents) => {
@@ -68,7 +68,7 @@ fn read_from_file_string() {
6868
let path = PathBuf::from("./test_string_read_file");
6969
let file_name = "out.keystore";
7070

71-
write_to_file(msg.as_bytes(), &path, file_name).unwrap();
71+
write_new_sensitive_file(msg.as_bytes(), &path.join(file_name)).unwrap();
7272
match std::fs::read_to_string(path.join(file_name)) {
7373
Ok(contents) => {
7474
cleanup_file(&path);

0 commit comments

Comments
 (0)