Skip to content

Commit fc8af31

Browse files
committed
Auto merge of rust-lang#3744 - newpavlov:nofollow, r=RalfJung
Add `O_NOFOLLOW` flag support
2 parents 0c1448d + 06a14f1 commit fc8af31

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

src/tools/miri/src/shims/unix/fs.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
266266

267267
let this = self.eval_context_mut();
268268

269-
let path = this.read_pointer(&args[0])?;
269+
let path_raw = this.read_pointer(&args[0])?;
270+
let path = this.read_path_from_c_str(path_raw)?;
270271
let flag = this.read_scalar(&args[1])?.to_i32()?;
271272

272273
let mut options = OpenOptions::new();
@@ -366,14 +367,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
366367
return Ok(-1);
367368
}
368369
}
370+
371+
let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
372+
if flag & o_nofollow == o_nofollow {
373+
#[cfg(unix)]
374+
{
375+
use std::os::unix::fs::OpenOptionsExt;
376+
options.custom_flags(libc::O_NOFOLLOW);
377+
}
378+
// Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:
379+
// the path could change between us checking it here and the later call to `open`.
380+
// But it's good enough for Miri purposes.
381+
#[cfg(not(unix))]
382+
{
383+
// O_NOFOLLOW only fails when the trailing component is a symlink;
384+
// the entire rest of the path can still contain symlinks.
385+
if path.is_symlink() {
386+
let eloop = this.eval_libc("ELOOP");
387+
this.set_last_error(eloop)?;
388+
return Ok(-1);
389+
}
390+
}
391+
mirror |= o_nofollow;
392+
}
393+
369394
// If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
370395
// then we throw an error.
371396
if flag != mirror {
372397
throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
373398
}
374399

375-
let path = this.read_path_from_c_str(path)?;
376-
377400
// Reject if isolation is enabled.
378401
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
379402
this.reject_in_isolation("`open`", reject_with)?;

src/tools/miri/tests/pass-dep/libc/libc-fs-readlink.rs renamed to src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ use std::os::unix::ffi::OsStrExt;
1111
mod utils;
1212

1313
fn main() {
14+
test_readlink();
15+
test_nofollow_symlink();
16+
}
17+
18+
fn test_readlink() {
1419
let bytes = b"Hello, World!\n";
1520
let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes);
1621
let expected_path = path.as_os_str().as_bytes();
@@ -49,3 +54,18 @@ fn main() {
4954
assert_eq!(res, -1);
5055
assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
5156
}
57+
58+
fn test_nofollow_symlink() {
59+
let bytes = b"Hello, World!\n";
60+
let path = utils::prepare_with_content("test_nofollow_symlink_target.txt", bytes);
61+
62+
let symlink_path = utils::prepare("test_nofollow_symlink.txt");
63+
std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
64+
65+
let symlink_cpath = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
66+
67+
let ret = unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) };
68+
assert_eq!(ret, -1);
69+
let err = Error::last_os_error().raw_os_error().unwrap();
70+
assert_eq!(err, libc::ELOOP);
71+
}

src/tools/miri/tests/pass-dep/libc/libc-fs.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ fn main() {
3737
test_sync_file_range();
3838
test_isatty();
3939
test_read_and_uninit();
40+
test_nofollow_not_symlink();
4041
}
4142

4243
fn test_file_open_unix_allow_two_args() {
@@ -423,3 +424,11 @@ fn test_read_and_uninit() {
423424
remove_file(&path).unwrap();
424425
}
425426
}
427+
428+
fn test_nofollow_not_symlink() {
429+
let bytes = b"Hello, World!\n";
430+
let path = utils::prepare_with_content("test_nofollow_not_symlink.txt", bytes);
431+
let cpath = CString::new(path.as_os_str().as_bytes()).unwrap();
432+
let ret = unsafe { libc::open(cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) };
433+
assert!(ret >= 0);
434+
}

0 commit comments

Comments
 (0)