diff options
Diffstat (limited to 'vendor/rustix/src/procfs.rs')
-rw-r--r-- | vendor/rustix/src/procfs.rs | 517 |
1 files changed, 0 insertions, 517 deletions
diff --git a/vendor/rustix/src/procfs.rs b/vendor/rustix/src/procfs.rs deleted file mode 100644 index 38f0911..0000000 --- a/vendor/rustix/src/procfs.rs +++ /dev/null @@ -1,517 +0,0 @@ -//! Utilities for working with `/proc`, where Linux's `procfs` is typically -//! mounted. -//! -//! `/proc` serves as an adjunct to Linux's main syscall surface area, -//! providing additional features with an awkward interface. -//! -//! This module does a considerable amount of work to determine whether `/proc` -//! is mounted, with actual `procfs`, and without any additional mount points -//! on top of the paths we open. -//! -//! Why all the effort to detect bind mount points? People are doing all kinds -//! of things with Linux containers these days, with many different privilege -//! schemes, and we want to avoid making any unnecessary assumptions. Rustix -//! and its users will sometimes use procfs *implicitly* (when Linux gives them -//! no better options), in ways that aren't obvious from their public APIs. -//! These filesystem accesses might not be visible to someone auditing the main -//! code of an application for places which may be influenced by the filesystem -//! namespace. So with the checking here, they may fail, but they won't be able -//! to succeed with bogus results. - -use crate::fd::{AsFd, BorrowedFd, OwnedFd}; -use crate::ffi::CStr; -use crate::fs::{ - fstat, fstatfs, major, openat, renameat, FileType, FsWord, Mode, OFlags, RawDir, Stat, CWD, - PROC_SUPER_MAGIC, -}; -use crate::io; -use crate::path::DecInt; -#[cfg(feature = "rustc-dep-of-std")] -use core::lazy::OnceCell; -use core::mem::MaybeUninit; -#[cfg(not(feature = "rustc-dep-of-std"))] -use once_cell::sync::OnceCell; - -/// Linux's procfs always uses inode 1 for its root directory. -const PROC_ROOT_INO: u64 = 1; - -// Identify an entry within "/proc", to determine which anomalies to check for. -#[derive(Copy, Clone, Debug)] -enum Kind { - Proc, - Pid, - Fd, - File, - Symlink, -} - -/// Check a subdirectory of "/proc" for anomalies. -fn check_proc_entry( - kind: Kind, - entry: BorrowedFd<'_>, - proc_stat: Option<&Stat>, -) -> io::Result<Stat> { - let entry_stat = fstat(entry)?; - check_proc_entry_with_stat(kind, entry, entry_stat, proc_stat) -} - -/// Check a subdirectory of "/proc" for anomalies, using the provided `Stat`. -fn check_proc_entry_with_stat( - kind: Kind, - entry: BorrowedFd<'_>, - entry_stat: Stat, - proc_stat: Option<&Stat>, -) -> io::Result<Stat> { - // Check the filesystem magic. - check_procfs(entry)?; - - match kind { - Kind::Proc => check_proc_root(entry, &entry_stat)?, - Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?, - Kind::File => check_proc_file(&entry_stat, proc_stat)?, - Kind::Symlink => check_proc_symlink(&entry_stat, proc_stat)?, - } - - // "/proc" directories are typically mounted r-xr-xr-x. - // "/proc/self/fd" is r-x------. Allow them to have fewer permissions, but - // not more. - match kind { - Kind::Symlink => { - // On Linux, symlinks don't have their own permissions. - } - _ => { - let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 }; - if entry_stat.st_mode & 0o777 & !expected_mode != 0 { - return Err(io::Errno::NOTSUP); - } - } - } - - match kind { - Kind::Fd => { - // Check that the "/proc/self/fd" directory doesn't have any - // extraneous links into it (which might include unexpected - // subdirectories). - if entry_stat.st_nlink != 2 { - return Err(io::Errno::NOTSUP); - } - } - Kind::Pid | Kind::Proc => { - // Check that the "/proc" and "/proc/self" directories aren't - // empty. - if entry_stat.st_nlink <= 2 { - return Err(io::Errno::NOTSUP); - } - } - Kind::File => { - // Check that files in procfs don't have extraneous hard links to - // them (which might indicate hard links to other things). - if entry_stat.st_nlink != 1 { - return Err(io::Errno::NOTSUP); - } - } - Kind::Symlink => { - // Check that symlinks in procfs don't have extraneous hard links - // to them (which might indicate hard links to other things). - if entry_stat.st_nlink != 1 { - return Err(io::Errno::NOTSUP); - } - } - } - - Ok(entry_stat) -} - -fn check_proc_root(entry: BorrowedFd<'_>, stat: &Stat) -> io::Result<()> { - // We use `O_DIRECTORY` for proc directories, so open should fail if we - // don't get a directory when we expect one. - assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory); - - // Check the root inode number. - if stat.st_ino != PROC_ROOT_INO { - return Err(io::Errno::NOTSUP); - } - - // Proc is a non-device filesystem, so check for major number 0. - // <https://www.kernel.org/doc/Documentation/admin-guide/devices.txt> - if major(stat.st_dev) != 0 { - return Err(io::Errno::NOTSUP); - } - - // Check that "/proc" is a mountpoint. - if !is_mountpoint(entry) { - return Err(io::Errno::NOTSUP); - } - - Ok(()) -} - -fn check_proc_subdir( - entry: BorrowedFd<'_>, - stat: &Stat, - proc_stat: Option<&Stat>, -) -> io::Result<()> { - // We use `O_DIRECTORY` for proc directories, so open should fail if we - // don't get a directory when we expect one. - assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory); - - check_proc_nonroot(stat, proc_stat)?; - - // Check that subdirectories of "/proc" are not mount points. - if is_mountpoint(entry) { - return Err(io::Errno::NOTSUP); - } - - Ok(()) -} - -fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> { - // Check that we have a regular file. - if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile { - return Err(io::Errno::NOTSUP); - } - - check_proc_nonroot(stat, proc_stat)?; - - Ok(()) -} - -fn check_proc_symlink(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> { - // Check that we have a symbolic link. - if FileType::from_raw_mode(stat.st_mode) != FileType::Symlink { - return Err(io::Errno::NOTSUP); - } - - check_proc_nonroot(stat, proc_stat)?; - - Ok(()) -} - -fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> { - // Check that we haven't been linked back to the root of "/proc". - if stat.st_ino == PROC_ROOT_INO { - return Err(io::Errno::NOTSUP); - } - - // Check that we're still in procfs. - if stat.st_dev != proc_stat.unwrap().st_dev { - return Err(io::Errno::NOTSUP); - } - - Ok(()) -} - -/// Check that `file` is opened on a `procfs` filesystem. -fn check_procfs(file: BorrowedFd<'_>) -> io::Result<()> { - let statfs = fstatfs(file)?; - let f_type = statfs.f_type; - if f_type != FsWord::from(PROC_SUPER_MAGIC) { - return Err(io::Errno::NOTSUP); - } - - Ok(()) -} - -/// Check whether the given directory handle is a mount point. -fn is_mountpoint(file: BorrowedFd<'_>) -> bool { - // We use a `renameat` call that would otherwise fail, but which fails with - // `XDEV` first if it would cross a mount point. - let err = renameat(file, cstr!("../."), file, cstr!(".")).unwrap_err(); - match err { - io::Errno::XDEV => true, // the rename failed due to crossing a mount point - io::Errno::BUSY => false, // the rename failed normally - _ => panic!("Unexpected error from `renameat`: {:?}", err), - } -} - -/// Open a directory in `/proc`, mapping all errors to `io::Errno::NOTSUP`. -fn proc_opendirat<P: crate::path::Arg, Fd: AsFd>(dirfd: Fd, path: P) -> io::Result<OwnedFd> { - // We don't add `PATH` here because that disables `DIRECTORY`. And we don't - // add `NOATIME` for the same reason as the comment in `open_and_check_file`. - let oflags = OFlags::NOFOLLOW | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY; - openat(dirfd, path, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP) -} - -/// Returns a handle to Linux's `/proc` directory. -/// -/// This ensures that `/proc` is procfs, that nothing is mounted on top of it, -/// and that it looks normal. It also returns the `Stat` of `/proc`. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -fn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { - static PROC: StaticFd = StaticFd::new(); - - // `OnceBox` is "racey" in that the initialization function may run - // multiple times. We're ok with that, since the initialization function - // has no side effects. - PROC.get_or_try_init(|| { - // Open "/proc". - let proc = proc_opendirat(CWD, cstr!("/proc"))?; - let proc_stat = - check_proc_entry(Kind::Proc, proc.as_fd(), None).map_err(|_err| io::Errno::NOTSUP)?; - - Ok(new_static_fd(proc, proc_stat)) - }) - .map(|(fd, stat)| (fd.as_fd(), stat)) -} - -/// Returns a handle to Linux's `/proc/self` directory. -/// -/// This ensures that `/proc/self` is procfs, that nothing is mounted on top of -/// it, and that it looks normal. It also returns the `Stat` of `/proc/self`. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -#[allow(unsafe_code)] -fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { - static PROC_SELF: StaticFd = StaticFd::new(); - - // The init function here may run multiple times; see above. - PROC_SELF - .get_or_try_init(|| { - let (proc, proc_stat) = proc()?; - - // `getpid` would return our pid in our own pid namespace, so - // instead use `readlink` on the `self` symlink to learn our pid in - // the procfs namespace. - let self_symlink = open_and_check_file(proc, proc_stat, cstr!("self"), Kind::Symlink)?; - let mut buf = [MaybeUninit::<u8>::uninit(); 20]; - let len = crate::backend::fs::syscalls::readlinkat( - self_symlink.as_fd(), - cstr!(""), - &mut buf, - )?; - let pid: &[u8] = unsafe { core::mem::transmute(&buf[..len]) }; - - // Open "/proc/self". Use our pid to compute the name rather than - // literally using "self", as "self" is a symlink. - let proc_self = proc_opendirat(proc, pid)?; - let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat)) - .map_err(|_err| io::Errno::NOTSUP)?; - - Ok(new_static_fd(proc_self, proc_self_stat)) - }) - .map(|(owned, stat)| (owned.as_fd(), stat)) -} - -/// Returns a handle to Linux's `/proc/self/fd` directory. -/// -/// This ensures that `/proc/self/fd` is `procfs`, that nothing is mounted on -/// top of it, and that it looks normal. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] -pub fn proc_self_fd() -> io::Result<BorrowedFd<'static>> { - static PROC_SELF_FD: StaticFd = StaticFd::new(); - - // The init function here may run multiple times; see above. - PROC_SELF_FD - .get_or_try_init(|| { - let (_, proc_stat) = proc()?; - - let (proc_self, _proc_self_stat) = proc_self()?; - - // Open "/proc/self/fd". - let proc_self_fd = proc_opendirat(proc_self, cstr!("fd"))?; - let proc_self_fd_stat = - check_proc_entry(Kind::Fd, proc_self_fd.as_fd(), Some(proc_stat)) - .map_err(|_err| io::Errno::NOTSUP)?; - - Ok(new_static_fd(proc_self_fd, proc_self_fd_stat)) - }) - .map(|(owned, _stat)| owned.as_fd()) -} - -type StaticFd = OnceCell<(OwnedFd, Stat)>; - -#[inline] -fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) { - (fd, stat) -} - -/// Returns a handle to Linux's `/proc/self/fdinfo` directory. -/// -/// This ensures that `/proc/self/fdinfo` is `procfs`, that nothing is mounted -/// on top of it, and that it looks normal. It also returns the `Stat` of -/// `/proc/self/fd`. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { - static PROC_SELF_FDINFO: StaticFd = StaticFd::new(); - - PROC_SELF_FDINFO - .get_or_try_init(|| { - let (_, proc_stat) = proc()?; - - let (proc_self, _proc_self_stat) = proc_self()?; - - // Open "/proc/self/fdinfo". - let proc_self_fdinfo = proc_opendirat(proc_self, cstr!("fdinfo"))?; - let proc_self_fdinfo_stat = - check_proc_entry(Kind::Fd, proc_self_fdinfo.as_fd(), Some(proc_stat)) - .map_err(|_err| io::Errno::NOTSUP)?; - - Ok((proc_self_fdinfo, proc_self_fdinfo_stat)) - }) - .map(|(owned, stat)| (owned.as_fd(), stat)) -} - -/// Returns a handle to a Linux `/proc/self/fdinfo/<fd>` file. -/// -/// This ensures that `/proc/self/fdinfo/<fd>` is `procfs`, that nothing is -/// mounted on top of it, and that it looks normal. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -#[inline] -#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] -pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> { - _proc_self_fdinfo(fd.as_fd()) -} - -fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> { - let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?; - let fd_str = DecInt::from_fd(fd); - open_and_check_file( - proc_self_fdinfo, - proc_self_fdinfo_stat, - fd_str.as_c_str(), - Kind::File, - ) -} - -/// Returns a handle to a Linux `/proc/self/pagemap` file. -/// -/// This ensures that `/proc/self/pagemap` is `procfs`, that nothing is -/// mounted on top of it, and that it looks normal. -/// -/// # References -/// - [Linux] -/// - [Linux pagemap] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -/// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt -#[inline] -#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] -pub fn proc_self_pagemap() -> io::Result<OwnedFd> { - proc_self_file(cstr!("pagemap")) -} - -/// Returns a handle to a Linux `/proc/self/maps` file. -/// -/// This ensures that `/proc/self/maps` is `procfs`, that nothing is -/// mounted on top of it, and that it looks normal. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -#[inline] -#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] -pub fn proc_self_maps() -> io::Result<OwnedFd> { - proc_self_file(cstr!("maps")) -} - -/// Returns a handle to a Linux `/proc/self/status` file. -/// -/// This ensures that `/proc/self/status` is `procfs`, that nothing is -/// mounted on top of it, and that it looks normal. -/// -/// # References -/// - [Linux] -/// -/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html -#[inline] -#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] -pub fn proc_self_status() -> io::Result<OwnedFd> { - proc_self_file(cstr!("status")) -} - -/// Open a file under `/proc/self`. -fn proc_self_file(name: &CStr) -> io::Result<OwnedFd> { - let (proc_self, proc_self_stat) = proc_self()?; - open_and_check_file(proc_self, proc_self_stat, name, Kind::File) -} - -/// Open a procfs file within in `dir` and check it for bind mounts. -fn open_and_check_file( - dir: BorrowedFd<'_>, - dir_stat: &Stat, - name: &CStr, - kind: Kind, -) -> io::Result<OwnedFd> { - let (_, proc_stat) = proc()?; - - // Don't use `NOATIME`, because it [requires us to own the file], and when - // a process sets itself non-dumpable Linux changes the user:group of its - // `/proc/<pid>` files [to root:root]. - // - // [requires us to own the file]: https://man7.org/linux/man-pages/man2/openat.2.html - // [to root:root]: https://man7.org/linux/man-pages/man5/proc.5.html - let mut oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY; - if let Kind::Symlink = kind { - // Open symlinks with `O_PATH`. - oflags |= OFlags::PATH; - } - let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?; - let file_stat = fstat(&file)?; - - // `is_mountpoint` only works on directory mount points, not file mount - // points. To detect file mount points, scan the parent directory to see if - // we can find a regular file with an inode and name that matches the file - // we just opened. If we can't find it, there could be a file bind mount on - // top of the file we want. - // - // TODO: With Linux 5.8 we might be able to use `statx` and - // `STATX_ATTR_MOUNT_ROOT` to detect mountpoints directly instead of doing - // this scanning. - - let expected_type = match kind { - Kind::File => FileType::RegularFile, - Kind::Symlink => FileType::Symlink, - _ => unreachable!(), - }; - - let mut found_file = false; - let mut found_dot = false; - - let mut buf = [MaybeUninit::uninit(); 2048]; - let mut iter = RawDir::new(dir, &mut buf); - while let Some(entry) = iter.next() { - let entry = entry.map_err(|_err| io::Errno::NOTSUP)?; - if entry.ino() == file_stat.st_ino - && entry.file_type() == expected_type - && entry.file_name() == name - { - // We found the file. Proceed to check the file handle. - let _ = check_proc_entry_with_stat(kind, file.as_fd(), file_stat, Some(proc_stat))?; - - found_file = true; - } else if entry.ino() == dir_stat.st_ino - && entry.file_type() == FileType::Directory - && entry.file_name() == cstr!(".") - { - // We found ".", and it's the right ".". - found_dot = true; - } - } - - if found_file && found_dot { - Ok(file) - } else { - Err(io::Errno::NOTSUP) - } -} |