1b8a62b91Sopenharmony_ci//! Utilities for working with `/proc`, where Linux's `procfs` is typically 2b8a62b91Sopenharmony_ci//! mounted. `/proc` serves as an adjunct to Linux's main syscall surface area, 3b8a62b91Sopenharmony_ci//! providing additional features with an awkward interface. 4b8a62b91Sopenharmony_ci//! 5b8a62b91Sopenharmony_ci//! This module does a considerable amount of work to determine whether `/proc` 6b8a62b91Sopenharmony_ci//! is mounted, with actual `procfs`, and without any additional mount points 7b8a62b91Sopenharmony_ci//! on top of the paths we open. 8b8a62b91Sopenharmony_ci//! 9b8a62b91Sopenharmony_ci//! Why all the effort to detect bind mount points? People are doing all kinds 10b8a62b91Sopenharmony_ci//! of things with Linux containers these days, with many different privilege 11b8a62b91Sopenharmony_ci//! schemes, and we want to avoid making any unnecessary assumptions. Rustix 12b8a62b91Sopenharmony_ci//! and its users will sometimes use procfs *implicitly* (when Linux gives them 13b8a62b91Sopenharmony_ci//! no better options), in ways that aren't obvious from their public APIs. 14b8a62b91Sopenharmony_ci//! These filesystem accesses might not be visible to someone auditing the main 15b8a62b91Sopenharmony_ci//! code of an application for places which may be influenced by the filesystem 16b8a62b91Sopenharmony_ci//! namespace. So with the checking here, they may fail, but they won't be able 17b8a62b91Sopenharmony_ci//! to succeed with bogus results. 18b8a62b91Sopenharmony_ci 19b8a62b91Sopenharmony_ciuse crate::fd::{AsFd, BorrowedFd, OwnedFd}; 20b8a62b91Sopenharmony_ciuse crate::ffi::CStr; 21b8a62b91Sopenharmony_ciuse crate::fs::{ 22b8a62b91Sopenharmony_ci cwd, fstat, fstatfs, major, openat, renameat, Dir, FileType, Mode, OFlags, Stat, 23b8a62b91Sopenharmony_ci PROC_SUPER_MAGIC, 24b8a62b91Sopenharmony_ci}; 25b8a62b91Sopenharmony_ciuse crate::io; 26b8a62b91Sopenharmony_ciuse crate::path::DecInt; 27b8a62b91Sopenharmony_ciuse crate::process::getpid; 28b8a62b91Sopenharmony_ci#[cfg(feature = "rustc-dep-of-std")] 29b8a62b91Sopenharmony_ciuse core::lazy::OnceCell; 30b8a62b91Sopenharmony_ci#[cfg(not(feature = "rustc-dep-of-std"))] 31b8a62b91Sopenharmony_ciuse once_cell::sync::OnceCell; 32b8a62b91Sopenharmony_ci 33b8a62b91Sopenharmony_ci/// Linux's procfs always uses inode 1 for its root directory. 34b8a62b91Sopenharmony_ciconst PROC_ROOT_INO: u64 = 1; 35b8a62b91Sopenharmony_ci 36b8a62b91Sopenharmony_ci// Identify an entry within "/proc", to determine which anomalies to check for. 37b8a62b91Sopenharmony_ci#[derive(Copy, Clone, Debug)] 38b8a62b91Sopenharmony_cienum Kind { 39b8a62b91Sopenharmony_ci Proc, 40b8a62b91Sopenharmony_ci Pid, 41b8a62b91Sopenharmony_ci Fd, 42b8a62b91Sopenharmony_ci File, 43b8a62b91Sopenharmony_ci} 44b8a62b91Sopenharmony_ci 45b8a62b91Sopenharmony_ci/// Check a subdirectory of "/proc" for anomalies. 46b8a62b91Sopenharmony_cifn check_proc_entry( 47b8a62b91Sopenharmony_ci kind: Kind, 48b8a62b91Sopenharmony_ci entry: BorrowedFd<'_>, 49b8a62b91Sopenharmony_ci proc_stat: Option<&Stat>, 50b8a62b91Sopenharmony_ci) -> io::Result<Stat> { 51b8a62b91Sopenharmony_ci let entry_stat = fstat(entry)?; 52b8a62b91Sopenharmony_ci check_proc_entry_with_stat(kind, entry, entry_stat, proc_stat) 53b8a62b91Sopenharmony_ci} 54b8a62b91Sopenharmony_ci 55b8a62b91Sopenharmony_ci/// Check a subdirectory of "/proc" for anomalies, using the provided `Stat`. 56b8a62b91Sopenharmony_cifn check_proc_entry_with_stat( 57b8a62b91Sopenharmony_ci kind: Kind, 58b8a62b91Sopenharmony_ci entry: BorrowedFd<'_>, 59b8a62b91Sopenharmony_ci entry_stat: Stat, 60b8a62b91Sopenharmony_ci proc_stat: Option<&Stat>, 61b8a62b91Sopenharmony_ci) -> io::Result<Stat> { 62b8a62b91Sopenharmony_ci // Check the filesystem magic. 63b8a62b91Sopenharmony_ci check_procfs(entry)?; 64b8a62b91Sopenharmony_ci 65b8a62b91Sopenharmony_ci match kind { 66b8a62b91Sopenharmony_ci Kind::Proc => check_proc_root(entry, &entry_stat)?, 67b8a62b91Sopenharmony_ci Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?, 68b8a62b91Sopenharmony_ci Kind::File => check_proc_file(&entry_stat, proc_stat)?, 69b8a62b91Sopenharmony_ci } 70b8a62b91Sopenharmony_ci 71b8a62b91Sopenharmony_ci // "/proc" directories are typically mounted r-xr-xr-x. 72b8a62b91Sopenharmony_ci // "/proc/self/fd" is r-x------. Allow them to have fewer permissions, but 73b8a62b91Sopenharmony_ci // not more. 74b8a62b91Sopenharmony_ci let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 }; 75b8a62b91Sopenharmony_ci if entry_stat.st_mode & 0o777 & !expected_mode != 0 { 76b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 77b8a62b91Sopenharmony_ci } 78b8a62b91Sopenharmony_ci 79b8a62b91Sopenharmony_ci match kind { 80b8a62b91Sopenharmony_ci Kind::Fd => { 81b8a62b91Sopenharmony_ci // Check that the "/proc/self/fd" directory doesn't have any extraneous 82b8a62b91Sopenharmony_ci // links into it (which might include unexpected subdirectories). 83b8a62b91Sopenharmony_ci if entry_stat.st_nlink != 2 { 84b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 85b8a62b91Sopenharmony_ci } 86b8a62b91Sopenharmony_ci } 87b8a62b91Sopenharmony_ci Kind::Pid | Kind::Proc => { 88b8a62b91Sopenharmony_ci // Check that the "/proc" and "/proc/self" directories aren't empty. 89b8a62b91Sopenharmony_ci if entry_stat.st_nlink <= 2 { 90b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 91b8a62b91Sopenharmony_ci } 92b8a62b91Sopenharmony_ci } 93b8a62b91Sopenharmony_ci Kind::File => { 94b8a62b91Sopenharmony_ci // Check that files in procfs don't have extraneous hard links to 95b8a62b91Sopenharmony_ci // them (which might indicate hard links to other things). 96b8a62b91Sopenharmony_ci if entry_stat.st_nlink != 1 { 97b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 98b8a62b91Sopenharmony_ci } 99b8a62b91Sopenharmony_ci } 100b8a62b91Sopenharmony_ci } 101b8a62b91Sopenharmony_ci 102b8a62b91Sopenharmony_ci Ok(entry_stat) 103b8a62b91Sopenharmony_ci} 104b8a62b91Sopenharmony_ci 105b8a62b91Sopenharmony_cifn check_proc_root(entry: BorrowedFd<'_>, stat: &Stat) -> io::Result<()> { 106b8a62b91Sopenharmony_ci // We use `O_DIRECTORY` for proc directories, so open should fail if we 107b8a62b91Sopenharmony_ci // don't get a directory when we expect one. 108b8a62b91Sopenharmony_ci assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory); 109b8a62b91Sopenharmony_ci 110b8a62b91Sopenharmony_ci // Check the root inode number. 111b8a62b91Sopenharmony_ci if stat.st_ino != PROC_ROOT_INO { 112b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 113b8a62b91Sopenharmony_ci } 114b8a62b91Sopenharmony_ci 115b8a62b91Sopenharmony_ci // Proc is a non-device filesystem, so check for major number 0. 116b8a62b91Sopenharmony_ci // <https://www.kernel.org/doc/Documentation/admin-guide/devices.txt> 117b8a62b91Sopenharmony_ci if major(stat.st_dev) != 0 { 118b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 119b8a62b91Sopenharmony_ci } 120b8a62b91Sopenharmony_ci 121b8a62b91Sopenharmony_ci // Check that "/proc" is a mountpoint. 122b8a62b91Sopenharmony_ci if !is_mountpoint(entry) { 123b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 124b8a62b91Sopenharmony_ci } 125b8a62b91Sopenharmony_ci 126b8a62b91Sopenharmony_ci Ok(()) 127b8a62b91Sopenharmony_ci} 128b8a62b91Sopenharmony_ci 129b8a62b91Sopenharmony_cifn check_proc_subdir( 130b8a62b91Sopenharmony_ci entry: BorrowedFd<'_>, 131b8a62b91Sopenharmony_ci stat: &Stat, 132b8a62b91Sopenharmony_ci proc_stat: Option<&Stat>, 133b8a62b91Sopenharmony_ci) -> io::Result<()> { 134b8a62b91Sopenharmony_ci // We use `O_DIRECTORY` for proc directories, so open should fail if we 135b8a62b91Sopenharmony_ci // don't get a directory when we expect one. 136b8a62b91Sopenharmony_ci assert_eq!(FileType::from_raw_mode(stat.st_mode), FileType::Directory); 137b8a62b91Sopenharmony_ci 138b8a62b91Sopenharmony_ci check_proc_nonroot(stat, proc_stat)?; 139b8a62b91Sopenharmony_ci 140b8a62b91Sopenharmony_ci // Check that subdirectories of "/proc" are not mount points. 141b8a62b91Sopenharmony_ci if is_mountpoint(entry) { 142b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 143b8a62b91Sopenharmony_ci } 144b8a62b91Sopenharmony_ci 145b8a62b91Sopenharmony_ci Ok(()) 146b8a62b91Sopenharmony_ci} 147b8a62b91Sopenharmony_ci 148b8a62b91Sopenharmony_cifn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> { 149b8a62b91Sopenharmony_ci // Check that we have a regular file. 150b8a62b91Sopenharmony_ci if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile { 151b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 152b8a62b91Sopenharmony_ci } 153b8a62b91Sopenharmony_ci 154b8a62b91Sopenharmony_ci check_proc_nonroot(stat, proc_stat)?; 155b8a62b91Sopenharmony_ci 156b8a62b91Sopenharmony_ci Ok(()) 157b8a62b91Sopenharmony_ci} 158b8a62b91Sopenharmony_ci 159b8a62b91Sopenharmony_cifn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> { 160b8a62b91Sopenharmony_ci // Check that we haven't been linked back to the root of "/proc". 161b8a62b91Sopenharmony_ci if stat.st_ino == PROC_ROOT_INO { 162b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 163b8a62b91Sopenharmony_ci } 164b8a62b91Sopenharmony_ci 165b8a62b91Sopenharmony_ci // Check that we're still in procfs. 166b8a62b91Sopenharmony_ci if stat.st_dev != proc_stat.unwrap().st_dev { 167b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 168b8a62b91Sopenharmony_ci } 169b8a62b91Sopenharmony_ci 170b8a62b91Sopenharmony_ci Ok(()) 171b8a62b91Sopenharmony_ci} 172b8a62b91Sopenharmony_ci 173b8a62b91Sopenharmony_ci/// Check that `file` is opened on a `procfs` filesystem. 174b8a62b91Sopenharmony_cifn check_procfs(file: BorrowedFd<'_>) -> io::Result<()> { 175b8a62b91Sopenharmony_ci let statfs = fstatfs(file)?; 176b8a62b91Sopenharmony_ci let f_type = statfs.f_type; 177b8a62b91Sopenharmony_ci if f_type != PROC_SUPER_MAGIC { 178b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 179b8a62b91Sopenharmony_ci } 180b8a62b91Sopenharmony_ci 181b8a62b91Sopenharmony_ci Ok(()) 182b8a62b91Sopenharmony_ci} 183b8a62b91Sopenharmony_ci 184b8a62b91Sopenharmony_ci/// Check whether the given directory handle is a mount point. We use a 185b8a62b91Sopenharmony_ci/// `renameat` call that would otherwise fail, but which fails with `EXDEV` 186b8a62b91Sopenharmony_ci/// first if it would cross a mount point. 187b8a62b91Sopenharmony_cifn is_mountpoint(file: BorrowedFd<'_>) -> bool { 188b8a62b91Sopenharmony_ci let err = renameat(file, cstr!("../."), file, cstr!(".")).unwrap_err(); 189b8a62b91Sopenharmony_ci match err { 190b8a62b91Sopenharmony_ci io::Errno::XDEV => true, // the rename failed due to crossing a mount point 191b8a62b91Sopenharmony_ci io::Errno::BUSY => false, // the rename failed normally 192b8a62b91Sopenharmony_ci _ => panic!("Unexpected error from `renameat`: {:?}", err), 193b8a62b91Sopenharmony_ci } 194b8a62b91Sopenharmony_ci} 195b8a62b91Sopenharmony_ci 196b8a62b91Sopenharmony_ci/// Open a directory in `/proc`, mapping all errors to `io::Errno::NOTSUP`. 197b8a62b91Sopenharmony_cifn proc_opendirat<P: crate::path::Arg, Fd: AsFd>(dirfd: Fd, path: P) -> io::Result<OwnedFd> { 198b8a62b91Sopenharmony_ci // We could add `PATH`|`NOATIME` here but Linux 2.6.32 doesn't support it. 199b8a62b91Sopenharmony_ci // Also for `NOATIME` see the comment in `open_and_check_file`. 200b8a62b91Sopenharmony_ci let oflags = OFlags::NOFOLLOW | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOCTTY; 201b8a62b91Sopenharmony_ci openat(dirfd, path, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP) 202b8a62b91Sopenharmony_ci} 203b8a62b91Sopenharmony_ci 204b8a62b91Sopenharmony_ci/// Returns a handle to Linux's `/proc` directory. 205b8a62b91Sopenharmony_ci/// 206b8a62b91Sopenharmony_ci/// This ensures that `/proc` is procfs, that nothing is mounted on top of it, 207b8a62b91Sopenharmony_ci/// and that it looks normal. It also returns the `Stat` of `/proc`. 208b8a62b91Sopenharmony_ci/// 209b8a62b91Sopenharmony_ci/// # References 210b8a62b91Sopenharmony_ci/// - [Linux] 211b8a62b91Sopenharmony_ci/// 212b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 213b8a62b91Sopenharmony_cifn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { 214b8a62b91Sopenharmony_ci static PROC: StaticFd = StaticFd::new(); 215b8a62b91Sopenharmony_ci 216b8a62b91Sopenharmony_ci // `OnceBox` is "racey" in that the initialization function may run 217b8a62b91Sopenharmony_ci // multiple times. We're ok with that, since the initialization function 218b8a62b91Sopenharmony_ci // has no side effects. 219b8a62b91Sopenharmony_ci PROC.get_or_try_init(|| { 220b8a62b91Sopenharmony_ci // Open "/proc". 221b8a62b91Sopenharmony_ci let proc = proc_opendirat(cwd(), cstr!("/proc"))?; 222b8a62b91Sopenharmony_ci let proc_stat = 223b8a62b91Sopenharmony_ci check_proc_entry(Kind::Proc, proc.as_fd(), None).map_err(|_err| io::Errno::NOTSUP)?; 224b8a62b91Sopenharmony_ci 225b8a62b91Sopenharmony_ci Ok(new_static_fd(proc, proc_stat)) 226b8a62b91Sopenharmony_ci }) 227b8a62b91Sopenharmony_ci .map(|(fd, stat)| (fd.as_fd(), stat)) 228b8a62b91Sopenharmony_ci} 229b8a62b91Sopenharmony_ci 230b8a62b91Sopenharmony_ci/// Returns a handle to Linux's `/proc/self` directory. 231b8a62b91Sopenharmony_ci/// 232b8a62b91Sopenharmony_ci/// This ensures that `/proc/self` is procfs, that nothing is mounted on top of 233b8a62b91Sopenharmony_ci/// it, and that it looks normal. It also returns the `Stat` of `/proc/self`. 234b8a62b91Sopenharmony_ci/// 235b8a62b91Sopenharmony_ci/// # References 236b8a62b91Sopenharmony_ci/// - [Linux] 237b8a62b91Sopenharmony_ci/// 238b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 239b8a62b91Sopenharmony_cifn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { 240b8a62b91Sopenharmony_ci static PROC_SELF: StaticFd = StaticFd::new(); 241b8a62b91Sopenharmony_ci 242b8a62b91Sopenharmony_ci // The init function here may run multiple times; see above. 243b8a62b91Sopenharmony_ci PROC_SELF 244b8a62b91Sopenharmony_ci .get_or_try_init(|| { 245b8a62b91Sopenharmony_ci let (proc, proc_stat) = proc()?; 246b8a62b91Sopenharmony_ci 247b8a62b91Sopenharmony_ci let pid = getpid(); 248b8a62b91Sopenharmony_ci 249b8a62b91Sopenharmony_ci // Open "/proc/self". Use our pid to compute the name rather than literally 250b8a62b91Sopenharmony_ci // using "self", as "self" is a symlink. 251b8a62b91Sopenharmony_ci let proc_self = proc_opendirat(proc, DecInt::new(pid.as_raw_nonzero().get()))?; 252b8a62b91Sopenharmony_ci let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat)) 253b8a62b91Sopenharmony_ci .map_err(|_err| io::Errno::NOTSUP)?; 254b8a62b91Sopenharmony_ci 255b8a62b91Sopenharmony_ci Ok(new_static_fd(proc_self, proc_self_stat)) 256b8a62b91Sopenharmony_ci }) 257b8a62b91Sopenharmony_ci .map(|(owned, stat)| (owned.as_fd(), stat)) 258b8a62b91Sopenharmony_ci} 259b8a62b91Sopenharmony_ci 260b8a62b91Sopenharmony_ci/// Returns a handle to Linux's `/proc/self/fd` directory. 261b8a62b91Sopenharmony_ci/// 262b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/fd` is `procfs`, that nothing is mounted on 263b8a62b91Sopenharmony_ci/// top of it, and that it looks normal. 264b8a62b91Sopenharmony_ci/// 265b8a62b91Sopenharmony_ci/// # References 266b8a62b91Sopenharmony_ci/// - [Linux] 267b8a62b91Sopenharmony_ci/// 268b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 269b8a62b91Sopenharmony_ci#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] 270b8a62b91Sopenharmony_cipub fn proc_self_fd() -> io::Result<BorrowedFd<'static>> { 271b8a62b91Sopenharmony_ci static PROC_SELF_FD: StaticFd = StaticFd::new(); 272b8a62b91Sopenharmony_ci 273b8a62b91Sopenharmony_ci // The init function here may run multiple times; see above. 274b8a62b91Sopenharmony_ci PROC_SELF_FD 275b8a62b91Sopenharmony_ci .get_or_try_init(|| { 276b8a62b91Sopenharmony_ci let (_, proc_stat) = proc()?; 277b8a62b91Sopenharmony_ci 278b8a62b91Sopenharmony_ci let (proc_self, _proc_self_stat) = proc_self()?; 279b8a62b91Sopenharmony_ci 280b8a62b91Sopenharmony_ci // Open "/proc/self/fd". 281b8a62b91Sopenharmony_ci let proc_self_fd = proc_opendirat(proc_self, cstr!("fd"))?; 282b8a62b91Sopenharmony_ci let proc_self_fd_stat = 283b8a62b91Sopenharmony_ci check_proc_entry(Kind::Fd, proc_self_fd.as_fd(), Some(proc_stat)) 284b8a62b91Sopenharmony_ci .map_err(|_err| io::Errno::NOTSUP)?; 285b8a62b91Sopenharmony_ci 286b8a62b91Sopenharmony_ci Ok(new_static_fd(proc_self_fd, proc_self_fd_stat)) 287b8a62b91Sopenharmony_ci }) 288b8a62b91Sopenharmony_ci .map(|(owned, _stat)| owned.as_fd()) 289b8a62b91Sopenharmony_ci} 290b8a62b91Sopenharmony_ci 291b8a62b91Sopenharmony_citype StaticFd = OnceCell<(OwnedFd, Stat)>; 292b8a62b91Sopenharmony_ci 293b8a62b91Sopenharmony_ci#[inline] 294b8a62b91Sopenharmony_cifn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) { 295b8a62b91Sopenharmony_ci (fd, stat) 296b8a62b91Sopenharmony_ci} 297b8a62b91Sopenharmony_ci 298b8a62b91Sopenharmony_ci/// Returns a handle to Linux's `/proc/self/fdinfo` directory. 299b8a62b91Sopenharmony_ci/// 300b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/fdinfo` is `procfs`, that nothing is mounted 301b8a62b91Sopenharmony_ci/// on top of it, and that it looks normal. It also returns the `Stat` of 302b8a62b91Sopenharmony_ci/// `/proc/self/fd`. 303b8a62b91Sopenharmony_ci/// 304b8a62b91Sopenharmony_ci/// # References 305b8a62b91Sopenharmony_ci/// - [Linux] 306b8a62b91Sopenharmony_ci/// 307b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 308b8a62b91Sopenharmony_cifn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> { 309b8a62b91Sopenharmony_ci static PROC_SELF_FDINFO: StaticFd = StaticFd::new(); 310b8a62b91Sopenharmony_ci 311b8a62b91Sopenharmony_ci PROC_SELF_FDINFO 312b8a62b91Sopenharmony_ci .get_or_try_init(|| { 313b8a62b91Sopenharmony_ci let (_, proc_stat) = proc()?; 314b8a62b91Sopenharmony_ci 315b8a62b91Sopenharmony_ci let (proc_self, _proc_self_stat) = proc_self()?; 316b8a62b91Sopenharmony_ci 317b8a62b91Sopenharmony_ci // Open "/proc/self/fdinfo". 318b8a62b91Sopenharmony_ci let proc_self_fdinfo = proc_opendirat(proc_self, cstr!("fdinfo"))?; 319b8a62b91Sopenharmony_ci let proc_self_fdinfo_stat = 320b8a62b91Sopenharmony_ci check_proc_entry(Kind::Fd, proc_self_fdinfo.as_fd(), Some(proc_stat)) 321b8a62b91Sopenharmony_ci .map_err(|_err| io::Errno::NOTSUP)?; 322b8a62b91Sopenharmony_ci 323b8a62b91Sopenharmony_ci Ok((proc_self_fdinfo, proc_self_fdinfo_stat)) 324b8a62b91Sopenharmony_ci }) 325b8a62b91Sopenharmony_ci .map(|(owned, stat)| (owned.as_fd(), stat)) 326b8a62b91Sopenharmony_ci} 327b8a62b91Sopenharmony_ci 328b8a62b91Sopenharmony_ci/// Returns a handle to a Linux `/proc/self/fdinfo/<fd>` file. 329b8a62b91Sopenharmony_ci/// 330b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/fdinfo/<fd>` is `procfs`, that nothing is 331b8a62b91Sopenharmony_ci/// mounted on top of it, and that it looks normal. 332b8a62b91Sopenharmony_ci/// 333b8a62b91Sopenharmony_ci/// # References 334b8a62b91Sopenharmony_ci/// - [Linux] 335b8a62b91Sopenharmony_ci/// 336b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 337b8a62b91Sopenharmony_ci#[inline] 338b8a62b91Sopenharmony_ci#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] 339b8a62b91Sopenharmony_cipub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> { 340b8a62b91Sopenharmony_ci _proc_self_fdinfo(fd.as_fd()) 341b8a62b91Sopenharmony_ci} 342b8a62b91Sopenharmony_ci 343b8a62b91Sopenharmony_cifn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> { 344b8a62b91Sopenharmony_ci let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?; 345b8a62b91Sopenharmony_ci let fd_str = DecInt::from_fd(fd); 346b8a62b91Sopenharmony_ci open_and_check_file(proc_self_fdinfo, proc_self_fdinfo_stat, fd_str.as_c_str()) 347b8a62b91Sopenharmony_ci} 348b8a62b91Sopenharmony_ci 349b8a62b91Sopenharmony_ci/// Returns a handle to a Linux `/proc/self/pagemap` file. 350b8a62b91Sopenharmony_ci/// 351b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/pagemap` is `procfs`, that nothing is 352b8a62b91Sopenharmony_ci/// mounted on top of it, and that it looks normal. 353b8a62b91Sopenharmony_ci/// 354b8a62b91Sopenharmony_ci/// # References 355b8a62b91Sopenharmony_ci/// - [Linux] 356b8a62b91Sopenharmony_ci/// - [Linux pagemap] 357b8a62b91Sopenharmony_ci/// 358b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 359b8a62b91Sopenharmony_ci/// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt 360b8a62b91Sopenharmony_ci#[inline] 361b8a62b91Sopenharmony_ci#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] 362b8a62b91Sopenharmony_cipub fn proc_self_pagemap() -> io::Result<OwnedFd> { 363b8a62b91Sopenharmony_ci proc_self_file(cstr!("pagemap")) 364b8a62b91Sopenharmony_ci} 365b8a62b91Sopenharmony_ci 366b8a62b91Sopenharmony_ci/// Returns a handle to a Linux `/proc/self/maps` file. 367b8a62b91Sopenharmony_ci/// 368b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/maps` is `procfs`, that nothing is 369b8a62b91Sopenharmony_ci/// mounted on top of it, and that it looks normal. 370b8a62b91Sopenharmony_ci/// 371b8a62b91Sopenharmony_ci/// # References 372b8a62b91Sopenharmony_ci/// - [Linux] 373b8a62b91Sopenharmony_ci/// 374b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 375b8a62b91Sopenharmony_ci#[inline] 376b8a62b91Sopenharmony_ci#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] 377b8a62b91Sopenharmony_cipub fn proc_self_maps() -> io::Result<OwnedFd> { 378b8a62b91Sopenharmony_ci proc_self_file(cstr!("maps")) 379b8a62b91Sopenharmony_ci} 380b8a62b91Sopenharmony_ci 381b8a62b91Sopenharmony_ci/// Returns a handle to a Linux `/proc/self/status` file. 382b8a62b91Sopenharmony_ci/// 383b8a62b91Sopenharmony_ci/// This ensures that `/proc/self/status` is `procfs`, that nothing is 384b8a62b91Sopenharmony_ci/// mounted on top of it, and that it looks normal. 385b8a62b91Sopenharmony_ci/// 386b8a62b91Sopenharmony_ci/// # References 387b8a62b91Sopenharmony_ci/// - [Linux] 388b8a62b91Sopenharmony_ci/// 389b8a62b91Sopenharmony_ci/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html 390b8a62b91Sopenharmony_ci#[inline] 391b8a62b91Sopenharmony_ci#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))] 392b8a62b91Sopenharmony_cipub fn proc_self_status() -> io::Result<OwnedFd> { 393b8a62b91Sopenharmony_ci proc_self_file(cstr!("status")) 394b8a62b91Sopenharmony_ci} 395b8a62b91Sopenharmony_ci 396b8a62b91Sopenharmony_ci/// Open a file under `/proc/self`. 397b8a62b91Sopenharmony_cifn proc_self_file(name: &CStr) -> io::Result<OwnedFd> { 398b8a62b91Sopenharmony_ci let (proc_self, proc_self_stat) = proc_self()?; 399b8a62b91Sopenharmony_ci open_and_check_file(proc_self, proc_self_stat, name) 400b8a62b91Sopenharmony_ci} 401b8a62b91Sopenharmony_ci 402b8a62b91Sopenharmony_ci/// Open a procfs file within in `dir` and check it for bind mounts. 403b8a62b91Sopenharmony_cifn open_and_check_file(dir: BorrowedFd, dir_stat: &Stat, name: &CStr) -> io::Result<OwnedFd> { 404b8a62b91Sopenharmony_ci let (_, proc_stat) = proc()?; 405b8a62b91Sopenharmony_ci 406b8a62b91Sopenharmony_ci // Don't use `NOATIME`, because it [requires us to own the file], and when 407b8a62b91Sopenharmony_ci // a process sets itself non-dumpable Linux changes the user:group of its 408b8a62b91Sopenharmony_ci // `/proc/<pid>` files [to root:root]. 409b8a62b91Sopenharmony_ci // 410b8a62b91Sopenharmony_ci // [requires us to own the file]: https://man7.org/linux/man-pages/man2/openat.2.html 411b8a62b91Sopenharmony_ci // [to root:root]: https://man7.org/linux/man-pages/man5/proc.5.html 412b8a62b91Sopenharmony_ci let oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY; 413b8a62b91Sopenharmony_ci let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?; 414b8a62b91Sopenharmony_ci let file_stat = fstat(&file)?; 415b8a62b91Sopenharmony_ci 416b8a62b91Sopenharmony_ci // `is_mountpoint` only works on directory mount points, not file mount 417b8a62b91Sopenharmony_ci // points. To detect file mount points, scan the parent directory to see 418b8a62b91Sopenharmony_ci // if we can find a regular file with an inode and name that matches the 419b8a62b91Sopenharmony_ci // file we just opened. If we can't find it, there could be a file bind 420b8a62b91Sopenharmony_ci // mount on top of the file we want. 421b8a62b91Sopenharmony_ci // 422b8a62b91Sopenharmony_ci // As we scan, we also check for ".", to make sure it's the same directory 423b8a62b91Sopenharmony_ci // as our original directory, to detect mount points, since 424b8a62b91Sopenharmony_ci // `Dir::read_from` reopens ".". 425b8a62b91Sopenharmony_ci // 426b8a62b91Sopenharmony_ci // TODO: With Linux 5.8 we might be able to use `statx` and 427b8a62b91Sopenharmony_ci // `STATX_ATTR_MOUNT_ROOT` to detect mountpoints directly instead of doing 428b8a62b91Sopenharmony_ci // this scanning. 429b8a62b91Sopenharmony_ci let dir = Dir::read_from(dir).map_err(|_err| io::Errno::NOTSUP)?; 430b8a62b91Sopenharmony_ci 431b8a62b91Sopenharmony_ci // Confirm that we got the same inode. 432b8a62b91Sopenharmony_ci let dot_stat = dir.stat().map_err(|_err| io::Errno::NOTSUP)?; 433b8a62b91Sopenharmony_ci if (dot_stat.st_dev, dot_stat.st_ino) != (dir_stat.st_dev, dir_stat.st_ino) { 434b8a62b91Sopenharmony_ci return Err(io::Errno::NOTSUP); 435b8a62b91Sopenharmony_ci } 436b8a62b91Sopenharmony_ci 437b8a62b91Sopenharmony_ci let mut found_file = false; 438b8a62b91Sopenharmony_ci let mut found_dot = false; 439b8a62b91Sopenharmony_ci for entry in dir { 440b8a62b91Sopenharmony_ci let entry = entry.map_err(|_err| io::Errno::NOTSUP)?; 441b8a62b91Sopenharmony_ci if entry.ino() == file_stat.st_ino 442b8a62b91Sopenharmony_ci && entry.file_type() == FileType::RegularFile 443b8a62b91Sopenharmony_ci && entry.file_name() == name 444b8a62b91Sopenharmony_ci { 445b8a62b91Sopenharmony_ci // We found the file. Proceed to check the file handle. 446b8a62b91Sopenharmony_ci let _ = 447b8a62b91Sopenharmony_ci check_proc_entry_with_stat(Kind::File, file.as_fd(), file_stat, Some(proc_stat))?; 448b8a62b91Sopenharmony_ci 449b8a62b91Sopenharmony_ci found_file = true; 450b8a62b91Sopenharmony_ci } else if entry.ino() == dir_stat.st_ino 451b8a62b91Sopenharmony_ci && entry.file_type() == FileType::Directory 452b8a62b91Sopenharmony_ci && entry.file_name() == cstr!(".") 453b8a62b91Sopenharmony_ci { 454b8a62b91Sopenharmony_ci // We found ".", and it's the right ".". 455b8a62b91Sopenharmony_ci found_dot = true; 456b8a62b91Sopenharmony_ci } 457b8a62b91Sopenharmony_ci } 458b8a62b91Sopenharmony_ci 459b8a62b91Sopenharmony_ci if found_file && found_dot { 460b8a62b91Sopenharmony_ci Ok(file) 461b8a62b91Sopenharmony_ci } else { 462b8a62b91Sopenharmony_ci Err(io::Errno::NOTSUP) 463b8a62b91Sopenharmony_ci } 464b8a62b91Sopenharmony_ci} 465