Skip to content

Make temporary directory names non-deterministic. #20488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions src/libstd/io/tempfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@

//! Temporary files and directories

use io::{fs, IoResult};
use io::{fs, IoError, IoErrorKind, IoResult};
use io;
use libc;
use iter::{IteratorExt, range};
use ops::Drop;
use option::Option;
use option::Option::{None, Some};
use os;
use path::{Path, GenericPath};
use rand::{Rng, thread_rng};
use result::Result::{Ok, Err};
use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
use str::StrExt;
use string::String;

/// A wrapper for a path to temporary directory implementing automatic
/// scope-based deletion.
Expand All @@ -31,7 +33,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
///
/// {
/// // create a temporary directory
/// let tmpdir = match TempDir::new("mysuffix") {
/// let tmpdir = match TempDir::new("myprefix") {
/// Ok(dir) => dir,
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
/// };
Expand All @@ -46,7 +48,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
/// }
/// {
/// // create a temporary directory, this time using a custom path
/// let tmpdir = match TempDir::new_in(&Path::new("/tmp/best/custom/path"), "mysuffix") {
/// let tmpdir = match TempDir::new_in(&Path::new("/tmp/best/custom/path"), "myprefix") {
/// Ok(dir) => dir,
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
/// };
Expand All @@ -61,7 +63,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
/// }
/// {
/// // create a temporary directory
/// let tmpdir = match TempDir::new("mysuffix") {
/// let tmpdir = match TempDir::new("myprefix") {
/// Ok(dir) => dir,
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
/// };
Expand All @@ -78,47 +80,59 @@ pub struct TempDir {
disarmed: bool
}

// How many times should we (re)try finding an unused random name? It should be
// enough that an attacker will run out of luck before we run out of patience.
const NUM_RETRIES: u32 = 1 << 31;
// How many characters should we include in a random file name? It needs to
// be enough to dissuade an attacker from trying to preemptively create names
// of that length, but not so huge that we unnecessarily drain the random number
// generator of entropy.
const NUM_RAND_CHARS: uint = 12;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto u32 or u8 in this case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iterator::take takes a uint, and this is just passed straight to take. It may or may not be correct for take to take a uint, but considering the signature of take isn't part of this PR.

If take is changed as part of the examine-integers stdlib pass that we're planning, then it will make sense for this constant to not be a uint and it will be updated then.


impl TempDir {
/// Attempts to make a temporary directory inside of `tmpdir` whose name
/// will have the suffix `suffix`. The directory will be automatically
/// will have the prefix `prefix`. The directory will be automatically
/// deleted once the returned wrapper is destroyed.
///
/// If no directory can be created, `Err` is returned.
pub fn new_in(tmpdir: &Path, suffix: &str) -> IoResult<TempDir> {
pub fn new_in(tmpdir: &Path, prefix: &str) -> IoResult<TempDir> {
if !tmpdir.is_absolute() {
let abs_tmpdir = try!(os::make_absolute(tmpdir));
return TempDir::new_in(&abs_tmpdir, suffix);
return TempDir::new_in(&abs_tmpdir, prefix);
}

static CNT: AtomicUint = ATOMIC_UINT_INIT;

let mut attempts = 0u;
loop {
let filename =
format!("rs-{}-{}-{}",
unsafe { libc::getpid() },
CNT.fetch_add(1, Ordering::SeqCst),
suffix);
let p = tmpdir.join(filename);
match fs::mkdir(&p, io::USER_RWX) {
Err(error) => {
if attempts >= 1000 {
return Err(error)
}
attempts += 1;
}
Ok(()) => return Ok(TempDir { path: Some(p), disarmed: false })
let mut rng = thread_rng();
for _ in range(0, NUM_RETRIES) {
let suffix: String = rng.gen_ascii_chars().take(NUM_RAND_CHARS).collect();
let leaf = if prefix.len() > 0 {
format!("{}.{}", prefix, suffix)
} else {
// If we're given an empty string for a prefix, then creating a
// directory starting with "." would lead to it being
// semi-invisible on some systems.
suffix
};
let path = tmpdir.join(leaf);
match fs::mkdir(&path, io::USER_RWX) {
Ok(_) => return Ok(TempDir { path: Some(path), disarmed: false }),
Err(IoError{kind:IoErrorKind::PathAlreadyExists,..}) => (),
Err(e) => return Err(e)
}
}

return Err(IoError{
kind: IoErrorKind::PathAlreadyExists,
desc:"Exhausted",
detail: None});
}

/// Attempts to make a temporary directory inside of `os::tmpdir()` whose
/// name will have the suffix `suffix`. The directory will be automatically
/// name will have the prefix `prefix`. The directory will be automatically
/// deleted once the returned wrapper is destroyed.
///
/// If no directory can be created, `Err` is returned.
pub fn new(suffix: &str) -> IoResult<TempDir> {
TempDir::new_in(&os::tmpdir(), suffix)
pub fn new(prefix: &str) -> IoResult<TempDir> {
TempDir::new_in(&os::tmpdir(), prefix)
}

/// Unwrap the wrapped `std::path::Path` from the `TempDir` wrapper.
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-pass/tempfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn test_tempdir() {
let path = {
let p = TempDir::new_in(&Path::new("."), "foobar").unwrap();
let p = p.path();
assert!(p.as_vec().ends_with(b"foobar"));
assert!(p.as_str().unwrap().contains("foobar"));
p.clone()
};
assert!(!path.exists());
Expand Down