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