From 4352bd62395b41a550dd083ac3e7162877e4f73d Mon Sep 17 00:00:00 2001 From: Cylae <13425054+Cylae@users.noreply.github.com> Date: Wed, 9 Sep 2026 00:54:47 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20use=20tempfile=20crate=20for=20s?= =?UTF-8?q?ecure=20temporary=20file=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server_manager/Cargo.lock | 43 ++++++++++++- server_manager/Cargo.toml | 1 + server_manager/src/core/atomic_io.rs | 94 ++++++++++++++-------------- 3 files changed, 90 insertions(+), 48 deletions(-) diff --git a/server_manager/Cargo.lock b/server_manager/Cargo.lock index bd31d10..3d99da4 100644 --- a/server_manager/Cargo.lock +++ b/server_manager/Cargo.lock @@ -623,6 +623,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "find-msvc-tools" version = "0.1.11" @@ -978,6 +984,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "lock_api" version = "0.4.14" @@ -1380,10 +1392,23 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1511,6 +1536,7 @@ dependencies = [ "serde_json", "serde_yaml_ng", "sysinfo", + "tempfile", "time", "tokio", "tower 0.4.13", @@ -1618,6 +1644,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2014,7 +2053,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] diff --git a/server_manager/Cargo.toml b/server_manager/Cargo.toml index a492a0d..1851850 100644 --- a/server_manager/Cargo.toml +++ b/server_manager/Cargo.toml @@ -27,6 +27,7 @@ time = "0.3" rand = "0.10.0" rpassword = "7.3" argon2 = "0.6.0" +tempfile = "3.8" [dev-dependencies] criterion = "0.5" diff --git a/server_manager/src/core/atomic_io.rs b/server_manager/src/core/atomic_io.rs index 07e2030..d07e82f 100644 --- a/server_manager/src/core/atomic_io.rs +++ b/server_manager/src/core/atomic_io.rs @@ -1,19 +1,20 @@ use anyhow::{Context, Result}; -use std::fs::{self, OpenOptions}; +use std::fs; use std::io::Write; use std::path::Path; +use tempfile::Builder; #[cfg(unix)] -use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::fs::PermissionsExt; -/// Atomically writes content to a file. +/// Atomically writes content to a file using secure temporary file creation. /// /// Steps: -/// 1. Creates a temporary file in the same directory as `path` (guaranteeing same filesystem/mount). +/// 1. Creates a secure temporary file in the same directory as `path` using `tempfile::Builder` (guaranteeing same filesystem/mount and unguessable filename). /// 2. Sets explicit permissions on creation (e.g. 0600 or 0644 on Unix). /// 3. Writes content and flushes buffers. /// 4. Synchronizes to disk via `fsync` (`sync_all`). -/// 5. Atomically renames the temporary file to the destination path. +/// 5. Atomically persists/renames the temporary file to the destination path. pub fn atomic_write>(path: P, content: &[u8], mode: u32) -> Result<()> { let dest = path.as_ref(); let parent = dest.parent().unwrap_or_else(|| Path::new(".")); @@ -27,55 +28,56 @@ pub fn atomic_write>(path: P, content: &[u8], mode: u32) -> Resul .map(|s| s.to_string_lossy().to_string()) .unwrap_or_else(|| "temp_file".to_string()); - let pid = std::process::id(); - let nanos = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_nanos(); + let prefix = format!(".tmp.{}.", file_name); - let tmp_name = format!(".tmp.{}.{}.{}", file_name, pid, nanos); - let tmp_path = parent.join(tmp_name); - - let write_result = (|| -> Result<()> { - let mut options = OpenOptions::new(); - options.write(true).create_new(true); - - #[cfg(unix)] - options.mode(mode); - - let mut file = options - .open(&tmp_path) - .with_context(|| format!("Failed to create temporary file {}", tmp_path.display()))?; - - file.write_all(content) - .with_context(|| format!("Failed to write content to {}", tmp_path.display()))?; - file.flush() - .with_context(|| format!("Failed to flush temporary file {}", tmp_path.display()))?; - file.sync_all() - .with_context(|| format!("Failed to fsync temporary file {}", tmp_path.display()))?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let _ = fs::set_permissions(&tmp_path, fs::Permissions::from_mode(mode)); - } + let mut temp_file = Builder::new() + .prefix(&prefix) + .tempfile_in(parent) + .with_context(|| { + format!( + "Failed to create secure temporary file in {}", + parent.display() + ) + })?; - fs::rename(&tmp_path, dest).with_context(|| { + #[cfg(unix)] + { + let perms = fs::Permissions::from_mode(mode); + fs::set_permissions(temp_file.path(), perms).with_context(|| { format!( - "Failed to atomically rename {} to {}", - tmp_path.display(), - dest.display() + "Failed to set permissions on temporary file {}", + temp_file.path().display() ) })?; + } - Ok(()) - })(); + temp_file.write_all(content).with_context(|| { + format!( + "Failed to write content to temporary file {}", + temp_file.path().display() + ) + })?; + temp_file.flush().with_context(|| { + format!( + "Failed to flush temporary file {}", + temp_file.path().display() + ) + })?; + temp_file.as_file().sync_all().with_context(|| { + format!( + "Failed to fsync temporary file {}", + temp_file.path().display() + ) + })?; - if write_result.is_err() && tmp_path.exists() { - let _ = fs::remove_file(&tmp_path); - } + temp_file.persist(dest).with_context(|| { + format!( + "Failed to atomically persist temporary file to {}", + dest.display() + ) + })?; - write_result + Ok(()) } /// Helper function to atomically write a string slice.