Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  procfs.rs   Sprache: unbekannt

 
//! 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, seek, FileType, FsWord, Mode, OFlags, RawDir,
    SeekFrom, 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;

    // Position the directory iteration at the start.
    seek(dir, SeekFrom::Start(0))?;

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

[ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge