Skip to content

Commit 2c44195

Browse files
committed
Make temporary directory names non-deterministic.
The previous scheme made it possible for another user/attacker to cause the temporary directory creation scheme to panic. All you needed to know was the pid of the process you wanted to target ('other_pid') and the suffix it was using (let's pretend it's 'sfx') and then code such as this would, in essence, DOS it: for i in range(0u, 1001) { let tp = &Path::new(format!("/tmp/rs-{}-{}-sfx", other_pid, i)); match fs::mkdir(tp, io::USER_RWX) { _ => () } } Since the scheme retried only 1000 times to create a temporary directory before dying, the next time the attacked process called TempDir::new("sfx") after that would typically cause a panic. Of course, you don't necessarily need an attacker to cause such a DOS: creating 1000 temporary directories without closing any of the previous would be enough to DOS yourself. This patch broadly follows the OpenBSD implementation of mkstemp. It uses the operating system's random number generator to produce random directory names that are impractical to guess (and, just in case someone manages to do that, it retries creating the directory for a long time before giving up; OpenBSD retries INT_MAX times, although 1<<31 seems enough to thwart even the most patient attacker). As a small additional change, this patch also makes the argument that TempDir::new takes a prefix rather than a suffix. This is because 1) it more closely matches what mkstemp and friends do 2) if you're going to have a deterministic part of a filename, you really want it at the beginning so that shell completion is useful.
1 parent 1f732ef commit 2c44195

File tree

2 files changed

+45
-31
lines changed

2 files changed

+45
-31
lines changed

src/libstd/io/tempfile.rs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010

1111
//! Temporary files and directories
1212
13-
use io::{fs, IoResult};
13+
use io::{fs, IoError, IoErrorKind, IoResult};
1414
use io;
15-
use libc;
15+
use iter::{IteratorExt, range};
1616
use ops::Drop;
1717
use option::Option;
1818
use option::Option::{None, Some};
1919
use os;
2020
use path::{Path, GenericPath};
21+
use rand::{Rng, thread_rng};
2122
use result::Result::{Ok, Err};
22-
use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
23+
use str::StrExt;
24+
use string::String;
2325

2426
/// A wrapper for a path to temporary directory implementing automatic
2527
/// scope-based deletion.
@@ -31,7 +33,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
3133
///
3234
/// {
3335
/// // create a temporary directory
34-
/// let tmpdir = match TempDir::new("mysuffix") {
36+
/// let tmpdir = match TempDir::new("myprefix") {
3537
/// Ok(dir) => dir,
3638
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
3739
/// };
@@ -46,7 +48,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
4648
/// }
4749
/// {
4850
/// // create a temporary directory, this time using a custom path
49-
/// let tmpdir = match TempDir::new_in(&Path::new("/tmp/best/custom/path"), "mysuffix") {
51+
/// let tmpdir = match TempDir::new_in(&Path::new("/tmp/best/custom/path"), "myprefix") {
5052
/// Ok(dir) => dir,
5153
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
5254
/// };
@@ -61,7 +63,7 @@ use sync::atomic::{AtomicUint, ATOMIC_UINT_INIT, Ordering};
6163
/// }
6264
/// {
6365
/// // create a temporary directory
64-
/// let tmpdir = match TempDir::new("mysuffix") {
66+
/// let tmpdir = match TempDir::new("myprefix") {
6567
/// Ok(dir) => dir,
6668
/// Err(e) => panic!("couldn't create temporary directory: {}", e)
6769
/// };
@@ -78,47 +80,59 @@ pub struct TempDir {
7880
disarmed: bool
7981
}
8082

83+
// How many times should we (re)try finding an unused random name? It should be
84+
// enough that an attacker will run out of luck before we run out of patience.
85+
const NUM_RETRIES: u32 = 1 << 31;
86+
// How many characters should we include in a random file name? It needs to
87+
// be enough to dissuade an attacker from trying to preemptively create names
88+
// of that length, but not so huge that we unnecessarily drain the random number
89+
// generator of entropy.
90+
const NUM_RAND_CHARS: uint = 12;
91+
8192
impl TempDir {
8293
/// Attempts to make a temporary directory inside of `tmpdir` whose name
83-
/// will have the suffix `suffix`. The directory will be automatically
94+
/// will have the prefix `prefix`. The directory will be automatically
8495
/// deleted once the returned wrapper is destroyed.
8596
///
8697
/// If no directory can be created, `Err` is returned.
87-
pub fn new_in(tmpdir: &Path, suffix: &str) -> IoResult<TempDir> {
98+
pub fn new_in(tmpdir: &Path, prefix: &str) -> IoResult<TempDir> {
8899
if !tmpdir.is_absolute() {
89100
let abs_tmpdir = try!(os::make_absolute(tmpdir));
90-
return TempDir::new_in(&abs_tmpdir, suffix);
101+
return TempDir::new_in(&abs_tmpdir, prefix);
91102
}
92103

93-
static CNT: AtomicUint = ATOMIC_UINT_INIT;
94-
95-
let mut attempts = 0u;
96-
loop {
97-
let filename =
98-
format!("rs-{}-{}-{}",
99-
unsafe { libc::getpid() },
100-
CNT.fetch_add(1, Ordering::SeqCst),
101-
suffix);
102-
let p = tmpdir.join(filename);
103-
match fs::mkdir(&p, io::USER_RWX) {
104-
Err(error) => {
105-
if attempts >= 1000 {
106-
return Err(error)
107-
}
108-
attempts += 1;
109-
}
110-
Ok(()) => return Ok(TempDir { path: Some(p), disarmed: false })
104+
let mut rng = thread_rng();
105+
for _ in range(0, NUM_RETRIES) {
106+
let suffix: String = rng.gen_ascii_chars().take(NUM_RAND_CHARS).collect();
107+
let leaf = if prefix.len() > 0 {
108+
format!("{}.{}", prefix, suffix)
109+
} else {
110+
// If we're given an empty string for a prefix, then creating a
111+
// directory starting with "." would lead to it being
112+
// semi-invisible on some systems.
113+
suffix
114+
};
115+
let path = tmpdir.join(leaf);
116+
match fs::mkdir(&path, io::USER_RWX) {
117+
Ok(_) => return Ok(TempDir { path: Some(path), disarmed: false }),
118+
Err(IoError{kind:IoErrorKind::PathAlreadyExists,..}) => (),
119+
Err(e) => return Err(e)
111120
}
112121
}
122+
123+
return Err(IoError{
124+
kind: IoErrorKind::PathAlreadyExists,
125+
desc:"Exhausted",
126+
detail: None});
113127
}
114128

115129
/// Attempts to make a temporary directory inside of `os::tmpdir()` whose
116-
/// name will have the suffix `suffix`. The directory will be automatically
130+
/// name will have the prefix `prefix`. The directory will be automatically
117131
/// deleted once the returned wrapper is destroyed.
118132
///
119133
/// If no directory can be created, `Err` is returned.
120-
pub fn new(suffix: &str) -> IoResult<TempDir> {
121-
TempDir::new_in(&os::tmpdir(), suffix)
134+
pub fn new(prefix: &str) -> IoResult<TempDir> {
135+
TempDir::new_in(&os::tmpdir(), prefix)
122136
}
123137

124138
/// Unwrap the wrapped `std::path::Path` from the `TempDir` wrapper.

src/test/run-pass/tempfile.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn test_tempdir() {
2929
let path = {
3030
let p = TempDir::new_in(&Path::new("."), "foobar").unwrap();
3131
let p = p.path();
32-
assert!(p.as_vec().ends_with(b"foobar"));
32+
assert!(p.as_str().unwrap().contains("foobar"));
3333
p.clone()
3434
};
3535
assert!(!path.exists());

0 commit comments

Comments
 (0)