about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/shims/unix/fs.rs29
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs (renamed from src/tools/miri/tests/pass-dep/libc/libc-fs-readlink.rs)20
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-fs.rs9
3 files changed, 55 insertions, 3 deletions
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index e34aa5c09df..d1218114e0d 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -266,7 +266,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let this = self.eval_context_mut();
 
-        let path = this.read_pointer(&args[0])?;
+        let path_raw = this.read_pointer(&args[0])?;
+        let path = this.read_path_from_c_str(path_raw)?;
         let flag = this.read_scalar(&args[1])?.to_i32()?;
 
         let mut options = OpenOptions::new();
@@ -366,14 +367,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 return Ok(-1);
             }
         }
+
+        let o_nofollow = this.eval_libc_i32("O_NOFOLLOW");
+        if flag & o_nofollow == o_nofollow {
+            #[cfg(unix)]
+            {
+                use std::os::unix::fs::OpenOptionsExt;
+                options.custom_flags(libc::O_NOFOLLOW);
+            }
+            // Strictly speaking, this emulation is not equivalent to the O_NOFOLLOW flag behavior:
+            // the path could change between us checking it here and the later call to `open`.
+            // But it's good enough for Miri purposes.
+            #[cfg(not(unix))]
+            {
+                // O_NOFOLLOW only fails when the trailing component is a symlink;
+                // the entire rest of the path can still contain symlinks.
+                if path.is_symlink() {
+                    let eloop = this.eval_libc("ELOOP");
+                    this.set_last_error(eloop)?;
+                    return Ok(-1);
+                }
+            }
+            mirror |= o_nofollow;
+        }
+
         // If `flag` is not equal to `mirror`, there is an unsupported option enabled in `flag`,
         // then we throw an error.
         if flag != mirror {
             throw_unsup_format!("unsupported flags {:#x}", flag & !mirror);
         }
 
-        let path = this.read_path_from_c_str(path)?;
-
         // Reject if isolation is enabled.
         if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
             this.reject_in_isolation("`open`", reject_with)?;
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs-readlink.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs
index d72edd7d9e3..619c6db3a29 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-fs-readlink.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-fs-symlink.rs
@@ -11,6 +11,11 @@ use std::os::unix::ffi::OsStrExt;
 mod utils;
 
 fn main() {
+    test_readlink();
+    test_nofollow_symlink();
+}
+
+fn test_readlink() {
     let bytes = b"Hello, World!\n";
     let path = utils::prepare_with_content("miri_test_fs_link_target.txt", bytes);
     let expected_path = path.as_os_str().as_bytes();
@@ -49,3 +54,18 @@ fn main() {
     assert_eq!(res, -1);
     assert_eq!(Error::last_os_error().kind(), ErrorKind::NotFound);
 }
+
+fn test_nofollow_symlink() {
+    let bytes = b"Hello, World!\n";
+    let path = utils::prepare_with_content("test_nofollow_symlink_target.txt", bytes);
+
+    let symlink_path = utils::prepare("test_nofollow_symlink.txt");
+    std::os::unix::fs::symlink(&path, &symlink_path).unwrap();
+
+    let symlink_cpath = CString::new(symlink_path.as_os_str().as_bytes()).unwrap();
+
+    let ret = unsafe { libc::open(symlink_cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) };
+    assert_eq!(ret, -1);
+    let err = Error::last_os_error().raw_os_error().unwrap();
+    assert_eq!(err, libc::ELOOP);
+}
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
index eddea92353e..5b2bbfbb27d 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
@@ -37,6 +37,7 @@ fn main() {
     test_sync_file_range();
     test_isatty();
     test_read_and_uninit();
+    test_nofollow_not_symlink();
 }
 
 fn test_file_open_unix_allow_two_args() {
@@ -423,3 +424,11 @@ fn test_read_and_uninit() {
         remove_file(&path).unwrap();
     }
 }
+
+fn test_nofollow_not_symlink() {
+    let bytes = b"Hello, World!\n";
+    let path = utils::prepare_with_content("test_nofollow_not_symlink.txt", bytes);
+    let cpath = CString::new(path.as_os_str().as_bytes()).unwrap();
+    let ret = unsafe { libc::open(cpath.as_ptr(), libc::O_NOFOLLOW | libc::O_CLOEXEC) };
+    assert!(ret >= 0);
+}