Skip to content

Commit fa67a86

Browse files
LegNeatoRalfJung
andcommitted
Add mkstemp shim for unix
Co-authored-by: Ralf Jung <[email protected]>
1 parent f7e67d8 commit fa67a86

File tree

5 files changed

+213
-1
lines changed

5 files changed

+213
-1
lines changed

src/shims/unix/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
161161
// fadvise is only informational, we can ignore it.
162162
this.write_null(dest)?;
163163
}
164+
"mkstemp" => {
165+
let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
166+
let result = this.mkstemp(template)?;
167+
this.write_scalar(Scalar::from_i32(result), dest)?;
168+
}
164169

165170
// Time related shims
166171
"gettimeofday" => {

src/shims/unix/fs.rs

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs::{
44
read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
55
};
66
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7-
use std::path::Path;
7+
use std::path::{Path, PathBuf};
88
use std::time::SystemTime;
99

1010
use log::trace;
@@ -13,6 +13,7 @@ use rustc_data_structures::fx::FxHashMap;
1313
use rustc_middle::ty::{self, layout::LayoutOf};
1414
use rustc_target::abi::{Align, Size};
1515

16+
use crate::shims::os_str::bytes_to_os_str;
1617
use crate::*;
1718
use shims::os_str::os_str_to_bytes;
1819
use shims::time::system_time_to_duration;
@@ -1741,6 +1742,134 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
17411742
this.set_last_error(enotty)?;
17421743
Ok(0)
17431744
}
1745+
1746+
fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1747+
use rand::seq::SliceRandom;
1748+
1749+
// POSIX defines the template string.
1750+
const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1751+
1752+
let this = self.eval_context_mut();
1753+
this.assert_target_os_is_unix("mkstemp");
1754+
1755+
// POSIX defines the maximum number of attempts before failure.
1756+
//
1757+
// `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1758+
// POSIX says this about `TMP_MAX`:
1759+
// * Minimum number of unique filenames generated by `tmpnam()`.
1760+
// * Maximum number of times an application can call `tmpnam()` reliably.
1761+
// * The value of `TMP_MAX` is at least 25.
1762+
// * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1763+
// See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1764+
let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1765+
1766+
// Get the raw bytes from the template -- as a byte slice, this is a string in the target
1767+
// (and the target is unix, so a byte slice is the right representation).
1768+
let template_ptr = this.read_pointer(template_op)?;
1769+
let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1770+
let template_bytes = template.as_mut_slice();
1771+
1772+
// Reject if isolation is enabled.
1773+
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1774+
this.reject_in_isolation("`mkstemp`", reject_with)?;
1775+
let eacc = this.eval_libc("EACCES")?;
1776+
this.set_last_error(eacc)?;
1777+
return Ok(-1);
1778+
}
1779+
1780+
// Get the bytes of the suffix we expect in _target_ encoding.
1781+
let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1782+
1783+
// At this point we have one `&[u8]` that represents the template and one `&[u8]`
1784+
// that represents the expected suffix.
1785+
1786+
// Now we figure out the index of the slice we expect to contain the suffix.
1787+
let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1788+
let end_pos = template_bytes.len();
1789+
let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1790+
1791+
// If we don't find the suffix, it is an error.
1792+
if last_six_char_bytes != suffix_bytes {
1793+
let einval = this.eval_libc("EINVAL")?;
1794+
this.set_last_error(einval)?;
1795+
return Ok(-1);
1796+
}
1797+
1798+
// At this point we know we have 6 ASCII 'X' characters as a suffix.
1799+
1800+
// From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1801+
const SUBSTITUTIONS: &[char; 62] = &[
1802+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1803+
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1804+
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1805+
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1806+
];
1807+
1808+
// The file is opened with specific options, which Rust does not expose in a portable way.
1809+
// So we use specific APIs depending on the host OS.
1810+
let mut fopts = OpenOptions::new();
1811+
fopts.read(true).write(true).create_new(true);
1812+
1813+
#[cfg(unix)]
1814+
{
1815+
use std::os::unix::fs::OpenOptionsExt;
1816+
fopts.mode(0o600);
1817+
// Do not allow others to read or modify this file.
1818+
fopts.custom_flags(libc::O_EXCL);
1819+
}
1820+
#[cfg(windows)]
1821+
{
1822+
use std::os::windows::fs::OpenOptionsExt;
1823+
// Do not allow others to read or modify this file.
1824+
fopts.share_mode(0);
1825+
}
1826+
1827+
// If the generated file already exists, we will try again `max_attempts` many times.
1828+
for _ in 0..max_attempts {
1829+
let rng = this.machine.rng.get_mut();
1830+
1831+
// Generate a random unique suffix.
1832+
let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1833+
1834+
// Replace the template string with the random string.
1835+
template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1836+
1837+
// Write the modified template back to the passed in pointer to maintain POSIX semantics.
1838+
this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1839+
1840+
// To actually open the file, turn this into a host OsString.
1841+
let p = bytes_to_os_str(template_bytes)?.to_os_string();
1842+
1843+
let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1844+
1845+
let file = fopts.open(&possibly_unique);
1846+
1847+
match file {
1848+
Ok(f) => {
1849+
let fh = &mut this.machine.file_handler;
1850+
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1851+
return Ok(fd);
1852+
}
1853+
Err(e) =>
1854+
match e.kind() {
1855+
// If the random file already exists, keep trying.
1856+
ErrorKind::AlreadyExists => continue,
1857+
// Any other errors are returned to the caller.
1858+
_ => {
1859+
// "On error, -1 is returned, and errno is set to
1860+
// indicate the error"
1861+
this.set_last_error_from_io_error(e.kind())?;
1862+
return Ok(-1);
1863+
}
1864+
},
1865+
}
1866+
}
1867+
1868+
// We ran out of attempts to create the file, return an error.
1869+
let eexist = this.eval_libc("EEXIST")?;
1870+
this.set_last_error(eexist)?;
1871+
Ok(-1)
1872+
}
17441873
}
17451874

17461875
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ignore-target-windows: No libc on Windows
2+
//@compile-flags: -Zmiri-disable-isolation
3+
4+
#![feature(rustc_private)]
5+
6+
fn main() {
7+
test_mkstemp_immutable_arg();
8+
}
9+
10+
fn test_mkstemp_immutable_arg() {
11+
let s: *mut libc::c_char = b"fooXXXXXX\0" as *const _ as *mut _;
12+
let _fd = unsafe { libc::mkstemp(s) }; //~ ERROR: Undefined Behavior: writing to alloc1 which is read-only
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: writing to ALLOC which is read-only
2+
--> $DIR/mkstemp_immutable_arg.rs:LL:CC
3+
|
4+
LL | let _fd = unsafe { libc::mkstemp(s) };
5+
| ^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: backtrace:
10+
= note: inside `test_mkstemp_immutable_arg` at $DIR/mkstemp_immutable_arg.rs:LL:CC
11+
note: inside `main` at $DIR/mkstemp_immutable_arg.rs:LL:CC
12+
--> $DIR/mkstemp_immutable_arg.rs:LL:CC
13+
|
14+
LL | test_mkstemp_immutable_arg();
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to previous error
20+

tests/pass/libc.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,56 @@ fn test_isatty() {
330330
}
331331
}
332332

333+
fn test_posix_mkstemp() {
334+
use std::ffi::CString;
335+
use std::ffi::OsStr;
336+
use std::os::unix::ffi::OsStrExt;
337+
use std::os::unix::io::FromRawFd;
338+
use std::path::Path;
339+
340+
let valid_template = "fooXXXXXX";
341+
// C needs to own this as `mkstemp(3)` says:
342+
// "Since it will be modified, `template` must not be a string constant, but
343+
// should be declared as a character array."
344+
// There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`.
345+
let ptr = CString::new(valid_template).unwrap().into_raw();
346+
let fd = unsafe { libc::mkstemp(ptr) };
347+
// Take ownership back in Rust to not leak memory.
348+
let slice = unsafe { CString::from_raw(ptr) };
349+
assert!(fd > 0);
350+
let osstr = OsStr::from_bytes(slice.to_bytes());
351+
let path: &Path = osstr.as_ref();
352+
let name = path.file_name().unwrap().to_string_lossy();
353+
assert!(name.ne("fooXXXXXX"));
354+
assert!(name.starts_with("foo"));
355+
assert_eq!(name.len(), 9);
356+
assert_eq!(
357+
name.chars().skip(3).filter(char::is_ascii_alphanumeric).collect::<Vec<char>>().len(),
358+
6
359+
);
360+
let file = unsafe { File::from_raw_fd(fd) };
361+
assert!(file.set_len(0).is_ok());
362+
363+
let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"];
364+
for t in invalid_templates {
365+
let ptr = CString::new(t).unwrap().into_raw();
366+
let fd = unsafe { libc::mkstemp(ptr) };
367+
let _ = unsafe { CString::from_raw(ptr) };
368+
// "On error, -1 is returned, and errno is set to
369+
// indicate the error"
370+
assert_eq!(fd, -1);
371+
let e = std::io::Error::last_os_error();
372+
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
373+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
374+
}
375+
}
376+
333377
fn main() {
334378
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
335379
test_posix_fadvise();
336380

337381
test_posix_gettimeofday();
382+
test_posix_mkstemp();
338383

339384
#[cfg(any(target_os = "linux"))]
340385
test_sync_file_range();

0 commit comments

Comments
 (0)