Skip to content

Commit

Permalink
Add support for openat2.
Browse files Browse the repository at this point in the history
Adds support for openat2 on linux. Includes a new ResolveFlag struct to
pass resolve flags, which is passed directly to the new fcntl::openat2
function. libc::open_how isn't exposed here, which may mean this API
needs to change if there's an update to openat2 to extend open_how with
new fields.
  • Loading branch information
blinsay committed Mar 20, 2024
1 parent c6a7d40 commit 54c97de
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,80 @@ pub fn openat<P: ?Sized + NixPath>(
Errno::result(fd)
}

#[cfg(target_os = "linux")]
libc_bitflags! {
/// Path resolution flags.
pub struct ResolveFlag: libc::c_ulonglong {
/// Do not permit the path resolution to succeed if any component of
/// the resolution is not a descendant of the directory indicated by
/// dirfd. This causes absolute symbolic links (and absolute values of
/// pathname) to be rejected.
RESOLVE_BENEATH;

/// Treat the directory referred to by dirfd as the root directory
/// while resolving pathname.
RESOLVE_IN_ROOT;

/// Disallow all magic-link resolution during path resolution. Magic
/// links are symbolic link-like objects that are most notably found
/// in proc(5); examples include `/proc/[pid]/exe` and `/proc/[pid]/fd/*`.
///
/// See symlink(7) for more details.
RESOLVE_NO_MAGICLINKS;

/// Disallow resolution of symbolic links during path resolution. This
/// option implies RESOLVE_NO_MAGICLINKS.
RESOLVE_NO_SYMLINKS;

/// Disallow traversal of mount points during path resolution (including
/// all bind mounts).
RESOLVE_NO_XDEV;
}
}

/// open or create a file for reading, writing or executing.
///
/// `openat2` is an extension of the [`openat`] function that allows the caller
/// to control how path resolution happens.
///
/// # See also
///
/// [openat2](https://man7.org/linux/man-pages/man2/openat2.2.html)
#[cfg(target_os = "linux")]
pub fn openat2<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
oflag: OFlag,
mode: Mode,
resolve: ResolveFlag,
) -> Result<RawFd> {
let mut how = open_how(oflag, mode, resolve);
let fd = path.with_nix_path(|cstr| unsafe {
libc::syscall(
libc::SYS_openat2,
dirfd,
cstr.as_ptr(),
&mut how as *mut _,
std::mem::size_of::<libc::open_how>(),
)
})?;
Errno::result(fd as i32)
}

#[cfg(target_os = "linux")]
fn open_how(oflag: OFlag, mode: Mode, resolve: ResolveFlag) -> libc::open_how {
use std::ptr::addr_of_mut;

let mut how = std::mem::MaybeUninit::<libc::open_how>::uninit();
let ptr = how.as_mut_ptr();
unsafe {
addr_of_mut!((*ptr).flags).write(oflag.bits() as libc::c_ulonglong);
addr_of_mut!((*ptr).mode).write(mode.bits() as libc::c_ulonglong);
addr_of_mut!((*ptr).resolve).write(resolve.bits());
how.assume_init()
}
}

/// Change the name of a file.
///
/// The `renameat` function is equivalent to `rename` except in the case where either `old_path`
Expand Down
56 changes: 56 additions & 0 deletions test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use nix::errno::*;
use nix::fcntl::{open, readlink, OFlag};
#[cfg(not(target_os = "redox"))]
use nix::fcntl::{openat, readlinkat, renameat};
#[cfg(target_os = "linux")]
use nix::fcntl::{openat2, ResolveFlag};
#[cfg(all(
target_os = "linux",
target_env = "gnu",
Expand Down Expand Up @@ -57,6 +59,60 @@ fn test_openat() {
close(dirfd).unwrap();
}

#[test]
#[cfg(target_os = "linux")]
// QEMU does not handle openat well enough to satisfy this test
// https://gitlab.com/qemu-project/qemu/-/issues/829
#[cfg_attr(qemu, ignore)]
fn test_openat2() {
const CONTENTS: &[u8] = b"abcd";
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(CONTENTS).unwrap();

let dirfd =
open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
.unwrap();

let fd = openat2(
dirfd,
tmp.path().file_name().unwrap(),
OFlag::O_RDONLY,
Mode::empty(),
ResolveFlag::RESOLVE_BENEATH,
)
.unwrap();

let mut buf = [0u8; 1024];
assert_eq!(4, read(fd, &mut buf).unwrap());
assert_eq!(CONTENTS, &buf[0..4]);

close(fd).unwrap();
close(dirfd).unwrap();
}

#[test]
#[cfg(target_os = "linux")]
fn test_openat2_forbidden() {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(b"let me out").unwrap();

let dirfd =
open(tmp.path().parent().unwrap(), OFlag::empty(), Mode::empty())
.unwrap();

let escape_attempt =
tmp.path().parent().unwrap().join("../../../hello.txt");

let res = openat2(
dirfd,
&escape_attempt,
OFlag::O_RDONLY,
Mode::empty(),
ResolveFlag::RESOLVE_BENEATH,
);
assert_eq!(Err(Errno::EXDEV), res);
}

#[test]
#[cfg(not(target_os = "redox"))]
fn test_renameat() {
Expand Down

0 comments on commit 54c97de

Please sign in to comment.