use crate::{
error::{Error, ErrorImpl},
flags::OpenFlags,
procfs,
syscalls::{self, OpenHow, ResolveFlags},
utils::{fd::proc_threadself_subpath, FdExt, MaybeOwnedFd},
};
use std::{
os::unix::{
fs::MetadataExt,
io::{AsRawFd, BorrowedFd, OwnedFd},
},
path::Path,
};
use rustix::fs::{Access, AtFlags};
#[derive(Copy, Clone, Debug)]
pub(crate) enum RawProcfsRoot<'fd> {
UnsafeGlobal,
UnsafeFd(BorrowedFd<'fd>),
}
impl<'fd> RawProcfsRoot<'fd> {
pub(crate) fn try_into_maybe_owned_fd<'a>(&'a self) -> Result<MaybeOwnedFd<'a, OwnedFd>, Error>
where
'a: 'fd,
{
let fd = match self {
Self::UnsafeGlobal => MaybeOwnedFd::OwnedFd(
syscalls::openat(
syscalls::BADFD,
"/proc",
OpenFlags::O_PATH | OpenFlags::O_DIRECTORY,
0,
)
.map_err(|err| ErrorImpl::RawOsError {
operation: "open /proc handle".into(),
source: err,
})?,
),
Self::UnsafeFd(fd) => MaybeOwnedFd::BorrowedFd(*fd),
};
procfs::verify_is_procfs_root(fd.as_fd())?;
Ok(fd)
}
pub(crate) fn exists_unchecked(&self, path: impl AsRef<Path>) -> Result<(), Error> {
syscalls::accessat(
self.try_into_maybe_owned_fd()?.as_fd(),
path,
Access::EXISTS,
AtFlags::SYMLINK_NOFOLLOW,
)
.map_err(|err| {
ErrorImpl::RawOsError {
operation: "check if subpath exists in raw procfs".into(),
source: err,
}
.into()
})
}
fn openat2_beneath(&self, path: impl AsRef<Path>, oflags: OpenFlags) -> Result<OwnedFd, Error> {
let path = path.as_ref();
syscalls::openat2(
self.try_into_maybe_owned_fd()?.as_fd(),
path,
OpenHow {
flags: oflags.bits() as _,
mode: 0,
resolve: (ResolveFlags::RESOLVE_NO_MAGICLINKS
| ResolveFlags::RESOLVE_NO_XDEV
| ResolveFlags::RESOLVE_BENEATH)
.bits(),
},
)
.map_err(|err| {
ErrorImpl::RawOsError {
operation: "open raw procfs subpath".into(),
source: err,
}
.into()
})
}
fn opath_beneath_unchecked(
&self,
path: impl AsRef<Path>,
oflags: OpenFlags,
) -> Result<OwnedFd, Error> {
let path = path.as_ref();
let proc_rootfd = self.try_into_maybe_owned_fd()?;
let proc_rootfd = proc_rootfd.as_fd();
let (opath_oflags, oflags) = (
oflags & OpenFlags::O_NOFOLLOW,
oflags & !OpenFlags::O_NOFOLLOW,
);
let opath = syscalls::openat(proc_rootfd, path, OpenFlags::O_PATH | opath_oflags, 0)
.map_err(|err| ErrorImpl::RawOsError {
operation: "preliminary open raw procfs subpath to check fstype".into(),
source: err,
})?;
procfs::verify_is_procfs(&opath)?;
let file = syscalls::openat_follow(
proc_rootfd,
proc_threadself_subpath(*self, &format!("fd/{}", opath.as_raw_fd())),
oflags,
0,
)
.map_err(|err| ErrorImpl::RawOsError {
operation: "re-open raw procfs subpath".into(),
source: err,
})?;
procfs::verify_is_procfs(&file)?;
if opath.metadata()?.ino() != file.metadata()?.ino() {
Err(ErrorImpl::SafetyViolation {
description: "fd has an inconsistent inode number after re-opening -- probably a manipulated procfs".into(),
})?;
}
Ok(file)
}
pub(crate) fn open_beneath(
&self,
path: impl AsRef<Path>,
mut oflags: OpenFlags,
) -> Result<OwnedFd, Error> {
let path = path.as_ref();
oflags.insert(OpenFlags::O_NOFOLLOW);
let fd = self.openat2_beneath(path, oflags).or_else(|err| {
if syscalls::openat2::openat2_is_not_supported() {
self.opath_beneath_unchecked(path, oflags)
} else {
Err(err)
}
})?;
procfs::verify_is_procfs(&fd)?;
Ok(fd)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorKind;
use pretty_assertions::assert_matches;
#[test]
fn exists_unchecked() {
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.exists_unchecked("nonexist")
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
r#"exists_unchecked("nonexist") -> ENOENT"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.exists_unchecked("uptime")
.map_err(|err| err.kind()),
Ok(()),
r#"exists_unchecked("uptime") -> Ok"#
);
}
#[test]
fn open_beneath() {
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.open_beneath("nonexist", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
r#"open_beneath("nonexist") -> ENOENT"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.open_beneath("self", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"open_beneath("self") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.open_beneath("self/cwd", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"open_beneath("self/cwd") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.open_beneath("self/status", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Ok(_),
r#"open_beneath("self/status") -> Ok"#
);
}
#[test]
fn openat2_beneath() {
if syscalls::openat2::openat2_is_not_supported() {
return; }
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.openat2_beneath("nonexist", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
r#"openat2_beneath("nonexist") -> ENOENT"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.openat2_beneath("self", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"openat2_beneath("self") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.openat2_beneath("self/cwd", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"openat2_beneath("self/cwd") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.openat2_beneath("self/status", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Ok(_),
r#"openat2_beneath("self/status") -> Ok"#
);
}
#[test]
fn opath_beneath_unchecked() {
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.opath_beneath_unchecked("nonexist", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ENOENT))),
r#"opath_beneath_unchecked("nonexist") -> ENOENT"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.opath_beneath_unchecked("self", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"opath_beneath_unchecked("self") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.opath_beneath_unchecked("self/cwd", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Err(ErrorKind::OsError(Some(libc::ELOOP))),
r#"opath_beneath_unchecked("self/cwd") -> ELOOP"#
);
assert_matches!(
RawProcfsRoot::UnsafeGlobal
.opath_beneath_unchecked("self/status", OpenFlags::O_RDONLY)
.map_err(|err| err.kind()),
Ok(_),
r#"opath_beneath_unchecked("self/status") -> Ok"#
);
}
}