Add O_NOFOLLOW flag support

This commit is contained in:
Артём Павлов [Artyom Pavlov] 2024-07-22 16:51:20 +03:00
parent 0c1448d75f
commit 69b9eab4ab
3 changed files with 55 additions and 3 deletions

View file

@ -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)?;

View file

@ -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 = io::Error::last_os_error().raw_os_error().unwrap();
assert_eq!(err, libc::ELOOP);
}

View file

@ -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);
}