Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for openat2. #2339

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/2339.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for openat2 on linux.
113 changes: 113 additions & 0 deletions src/fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,119 @@ pub fn openat<P: ?Sized + NixPath>(
Errno::result(fd)
}

cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
libc_bitflags! {
/// Path resolution flags.
///
/// See [path resolution(7)](https://man7.org/linux/man-pages/man7/path_resolution.7.html)
/// for details of the resolution process.
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;
}
}

/// Specifies how [openat2] should open a pathname.
///
/// See <https://man7.org/linux/man-pages/man2/open_how.2type.html>
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct OpenHow(libc::open_how);

impl OpenHow {
/// Create a new zero-filled `open_how`.
pub fn new() -> Self {
// safety: according to the man page, open_how MUST be zero-initialized
// on init so that unknown fields are also zeroed.
Self(unsafe {
std::mem::MaybeUninit::zeroed().assume_init()
})
}

/// Set the open flags used to open a file, completely overwriting any
/// existing flags.
pub fn flags(mut self, flags: OFlag) -> Self {
let flags = flags.bits() as libc::c_ulonglong;
self.0.flags = flags;
self
}

/// Set the file mode new files will be created with, overwriting any
/// existing flags.
pub fn mode(mut self, mode: Mode) -> Self {
let mode = mode.bits() as libc::c_ulonglong;
self.0.mode = mode;
self
}

/// Set resolve flags, completely overwriting any existing flags.
///
/// See [ResolveFlag] for more detail.
pub fn resolve(mut self, resolve: ResolveFlag) -> Self {
let resolve = resolve.bits();
self.0.resolve = resolve;
self
}
}

// safety: default isn't derivable because libc::open_how must be zeroed
impl Default for OpenHow {
fn default() -> Self {
Self::new()
}
}

/// 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)
pub fn openat2<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
mut how: OpenHow,
) -> Result<RawFd> {
let fd = path.with_nix_path(|cstr| unsafe {
libc::syscall(
libc::SYS_openat2,
dirfd,
cstr.as_ptr(),
&mut how as *mut OpenHow,
std::mem::size_of::<libc::open_how>(),
)
})?;

Errno::result(fd as RawFd)
}
}
}

/// Change the name of a file.
///
/// The `renameat` function is equivalent to `rename` except in the case where either `old_path`
Expand Down
62 changes: 62 additions & 0 deletions test/test_fcntl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ 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, OpenHow, ResolveFlag};

#[cfg(all(
target_os = "linux",
target_env = "gnu",
Expand Down Expand Up @@ -57,6 +61,64 @@ 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(),
OpenHow::new()
.flags(OFlag::O_RDONLY)
.mode(Mode::empty())
.resolve(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")]
// 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_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,
OpenHow::new()
.flags(OFlag::O_RDONLY)
.resolve(ResolveFlag::RESOLVE_BENEATH),
);
assert_eq!(Err(Errno::EXDEV), res);
}

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