From 0491ea04553f41b74ee4f37b71d4d202025cbc34 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 17:43:35 -0800 Subject: [PATCH 1/7] cap-tempfile: Don't create anonymous files with 0o666 When creating anonymous temporary files, use mode 0o000 instead of 0o666 to further discourage anything else from opening them. This fixes a bug pointed out in #390. --- cap-tempfile/src/tempfile.rs | 37 ++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 73c2b932..9df71b74 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -68,9 +68,17 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result> { if anonymous { oflags |= OFlags::EXCL; } - // We default to 0o666, same as main rust when creating new files; this will be - // modified by umask: - let mode = Mode::from_raw_mode(0o666); + // For anonymous files, open with no permissions to discourage other + // processes from opening them. + // + // For named files, default to 0o666, same as main rust when creating new + // files; this will be modified by umask: + // + let mode = if anonymous { + Mode::from_raw_mode(0o000) + } else { + Mode::from_raw_mode(0o666) + }; // Happy path - Linux with O_TMPFILE match rustix::fs::openat(d, ".", oflags, mode) { Ok(r) => Ok(Some(File::from(r))), @@ -111,6 +119,16 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option)> opts.read(true); opts.write(true); opts.create_new(true); + #[cfg(unix)] + if anonymous { + use cap_std::fs::OpenOptionsExt; + opts.mode(0); + } + #[cfg(windows)] + if anonymous { + use cap_std::fs::OpenOptionsExt; + opts.share_mode(0); + } let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| { d.open_with(name, &opts) })?; @@ -262,7 +280,7 @@ mod test { let mut tf = TempFile::new(&td)?; // Test that we created with the right permissions - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(unix)] { use cap_std::fs_utf8::MetadataExt; use rustix::fs::Mode; @@ -291,6 +309,17 @@ mod test { let mut buf = String::new(); tf.read_to_string(&mut buf).unwrap(); assert_eq!(&buf, "hello world, I'm anonymous"); + + // Test that we created with the right permissions + #[cfg(unix)] + { + use cap_std::fs_utf8::MetadataExt; + use rustix::fs::Mode; + let metadata = tf.metadata().unwrap(); + let mode = metadata.mode(); + let mode = Mode::from_bits_truncate(mode); + assert_eq!(0o000, mode.bits() & 0o777); + } } else if cfg!(windows) { eprintln!("notice: Detected older Windows"); } From 8aed312f448912a2ceae3d9c5e6dbccef67b0564 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 17:56:25 -0800 Subject: [PATCH 2/7] Fix anonymous files on Windows. --- cap-tempfile/Cargo.toml | 2 +- cap-tempfile/src/tempfile.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cap-tempfile/Cargo.toml b/cap-tempfile/Cargo.toml index a5a0beef..dd23d915 100644 --- a/cap-tempfile/Cargo.toml +++ b/cap-tempfile/Cargo.toml @@ -26,7 +26,7 @@ rustix = { version = "1.0.0" } [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] rustix-linux-procfs = "0.1.1" -[target.'cfg(windows)'.dev-dependencies.windows-sys] +[target.'cfg(windows)'.dependencies.windows-sys] version = ">=0.60, <0.62" features = [ "Win32_Foundation", diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 9df71b74..4703b42c 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -127,13 +127,21 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option)> #[cfg(windows)] if anonymous { use cap_std::fs::OpenOptionsExt; + use windows_sys::Win32::Storage::FileSystem::{ + FILE_ATTRIBUTE_TEMPORARY, FILE_FLAG_DELETE_ON_CLOSE, + }; opts.share_mode(0); + opts.custom_flags(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE); } let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| { d.open_with(name, &opts) })?; if anonymous { - d.remove_file(name)?; + // On Windows we use `FILE_FLAG_DELETE_ON_CLOSE` instead. + #[cfg(not(windows))] + { + d.remove_file(name)?; + } Ok((f, None)) } else { Ok((f, Some(name))) From dcae524e48daa7e931325592fab651eff05d6d51 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 18:09:18 -0800 Subject: [PATCH 3/7] Fix umask tests on non-Linux Unix platforms. --- cap-tempfile/Cargo.toml | 3 +++ cap-tempfile/src/tempfile.rs | 34 +++++++++++++++------------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cap-tempfile/Cargo.toml b/cap-tempfile/Cargo.toml index dd23d915..89ef50a9 100644 --- a/cap-tempfile/Cargo.toml +++ b/cap-tempfile/Cargo.toml @@ -32,6 +32,9 @@ features = [ "Win32_Foundation", ] +[dev-dependencies] +tempfile = "3.23.0" + [features] default = [] fs_utf8 = ["cap-std/fs_utf8", "camino"] diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 4703b42c..4b6f2f17 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -243,26 +243,22 @@ mod test { use super::*; /// On Unix, calling `umask()` actually *mutates* the process global state. - /// This uses Linux `/proc` to read the current value. - #[cfg(any(target_os = "android", target_os = "linux"))] + /// This uses a temporary file instead. + #[cfg(unix)] fn get_process_umask() -> io::Result { - use io::BufRead; - let status = std::fs::File::open("/proc/self/status")?; - let bufr = io::BufReader::new(status); - for line in bufr.lines() { - let line = line?; - let l = if let Some(v) = line.split_once(':') { - v - } else { - continue; - }; - let (k, v) = l; - if k != "Umask" { - continue; - } - return Ok(u32::from_str_radix(v.trim(), 8).unwrap()); - } - panic!("Could not determine process umask") + use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; + + let d = tempfile::tempdir().unwrap(); + let p = d.path().join("file"); + + let mut opts = std::fs::OpenOptions::new(); + opts.read(true); + opts.write(true); + opts.create_new(true); + opts.mode(0o777); + let f = opts.open(p).unwrap(); + let m = f.metadata().unwrap(); + Ok(!m.mode() & 0o777) } /// Older Windows versions don't support removing open files From cc6ac302d32602914bd4d226949e9c025fe03cd5 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 18:14:10 -0800 Subject: [PATCH 4/7] Fix tempfile path. --- cap-tempfile/src/tempfile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 4b6f2f17..a7fa5b93 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -248,7 +248,7 @@ mod test { fn get_process_umask() -> io::Result { use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; - let d = tempfile::tempdir().unwrap(); + let d = ::tempfile::tempdir().unwrap(); let p = d.path().join("file"); let mut opts = std::fs::OpenOptions::new(); From 2d206fb7dac4a2d2329755f76c61a0921cf61e20 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 19:48:24 -0800 Subject: [PATCH 5/7] Fix compilation on MSRV. --- cap-tempfile/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cap-tempfile/src/lib.rs b/cap-tempfile/src/lib.rs index 2e381e39..bd7e35d8 100644 --- a/cap-tempfile/src/lib.rs +++ b/cap-tempfile/src/lib.rs @@ -19,7 +19,7 @@ use uuid::Uuid; pub mod utf8; mod tempfile; -pub use tempfile::*; +pub use crate::tempfile::*; /// Re-export because we use this in our public API. pub use cap_std; From f6434aae05c3fb7983678aeaf891c552cbdd71e4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 20:21:35 -0800 Subject: [PATCH 6/7] Fix compilation on Darwin. --- cap-tempfile/src/tempfile.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index a7fa5b93..91f3529d 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -291,7 +291,7 @@ mod test { let umask = get_process_umask()?; let metadata = tf.as_file().metadata().unwrap(); let mode = metadata.mode(); - let mode = Mode::from_bits_truncate(mode); + let mode = Mode::from_bits_truncate(mode as _); assert_eq!(0o666 & !umask, mode.bits() & 0o777); } // And that we can write From c643df2b93e8023d7bfb2c157e2be0a435a3e35f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 10 Dec 2025 20:26:39 -0800 Subject: [PATCH 7/7] More. --- cap-tempfile/src/tempfile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cap-tempfile/src/tempfile.rs b/cap-tempfile/src/tempfile.rs index 91f3529d..293c3b31 100644 --- a/cap-tempfile/src/tempfile.rs +++ b/cap-tempfile/src/tempfile.rs @@ -292,7 +292,7 @@ mod test { let metadata = tf.as_file().metadata().unwrap(); let mode = metadata.mode(); let mode = Mode::from_bits_truncate(mode as _); - assert_eq!(0o666 & !umask, mode.bits() & 0o777); + assert_eq!(0o666 & !umask, (mode.bits() & 0o777) as _); } // And that we can write tf.write_all(b"hello world")?; @@ -321,7 +321,7 @@ mod test { use rustix::fs::Mode; let metadata = tf.metadata().unwrap(); let mode = metadata.mode(); - let mode = Mode::from_bits_truncate(mode); + let mode = Mode::from_bits_truncate(mode as _); assert_eq!(0o000, mode.bits() & 0o777); } } else if cfg!(windows) {