Skip to content

Commit fc756ba

Browse files
committed
Implement proc_self_maps, proc_self_pagemap, and proc_self_fdinfo.
Add support for opening files within procfs, and add public APIs for accessing `/proc/self/maps`, `/proc/self/pagemap`, and `proc/self/<fd>/fdinfo`.
1 parent aeb2a6b commit fc756ba

File tree

3 files changed

+178
-3
lines changed

3 files changed

+178
-3
lines changed

src/io/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub use pipe::{pipe_with, PipeFlags};
8585
#[cfg(not(windows))]
8686
pub use poll::{poll, PollFd, PollFlags};
8787
#[cfg(all(feature = "procfs", any(target_os = "android", target_os = "linux")))]
88-
pub use procfs::proc_self_fd;
88+
pub use procfs::{proc_self_fd, proc_self_fdinfo_fd, proc_self_maps, proc_self_pagemap};
8989
#[cfg(not(windows))]
9090
pub use read_write::{pread, pwrite, read, readv, write, writev};
9191
#[cfg(not(any(windows, target_os = "redox")))]

src/io/procfs.rs

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
//! is mounted, with actual `procfs`, and without any additional mount points
77
//! on top of the paths we open.
88
9+
use crate::fd::{AsFd, BorrowedFd};
10+
use crate::ffi::ZStr;
911
use crate::fs::{
10-
cwd, fstat, fstatfs, major, openat, renameat, FileType, Mode, OFlags, Stat, PROC_SUPER_MAGIC,
12+
cwd, fstat, fstatfs, major, openat, renameat, Dir, FileType, Mode, OFlags, Stat,
13+
PROC_SUPER_MAGIC,
1114
};
12-
use crate::imp::fd::{AsFd, BorrowedFd};
1315
use crate::io::{self, OwnedFd};
1416
use crate::path::DecInt;
1517
use crate::process::{getgid, getpid, getuid, Gid, RawGid, RawUid, Uid};
@@ -27,6 +29,7 @@ enum Kind {
2729
Proc,
2830
Pid,
2931
Fd,
32+
File,
3033
}
3134

3235
/// Check a subdirectory of "/proc" for anomalies.
@@ -56,6 +59,7 @@ fn check_proc_entry_with_stat(
5659
match kind {
5760
Kind::Proc => check_proc_root(entry, &entry_stat)?,
5861
Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?,
62+
Kind::File => check_proc_file(&entry_stat, proc_stat)?,
5963
}
6064

6165
// Check the ownership of the directory.
@@ -85,6 +89,13 @@ fn check_proc_entry_with_stat(
8589
return Err(io::Error::NOTSUP);
8690
}
8791
}
92+
Kind::File => {
93+
// Check that files in procfs don't have extraneous hard links to
94+
// them (which might indicate hard links to other things).
95+
if entry_stat.st_nlink != 1 {
96+
return Err(io::Error::NOTSUP);
97+
}
98+
}
8899
}
89100

90101
Ok(entry_stat)
@@ -133,6 +144,17 @@ fn check_proc_subdir(
133144
Ok(())
134145
}
135146

147+
fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
148+
// Check that we have a regular file.
149+
if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile {
150+
return Err(io::Error::NOTSUP);
151+
}
152+
153+
check_proc_nonroot(stat, proc_stat)?;
154+
155+
Ok(())
156+
}
157+
136158
fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
137159
// Check that we haven't been linked back to the root of "/proc".
138160
if stat.st_ino == PROC_ROOT_INO {
@@ -306,3 +328,151 @@ type StaticFd = OnceCell<(OwnedFd, Stat)>;
306328
fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) {
307329
(fd, stat)
308330
}
331+
332+
/// Returns a handle to Linux's `/proc/self/fdinfo` directory.
333+
///
334+
/// This ensures that `/proc/self/fdinfo` is `procfs`, that nothing is mounted
335+
/// on top of it, and that it looks normal. It also returns the `Stat` of
336+
/// `/proc/self/fd`.
337+
///
338+
/// # References
339+
/// - [Linux]
340+
///
341+
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
342+
fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
343+
static PROC_SELF_FDINFO: OnceCell<(OwnedFd, Stat)> = OnceCell::new();
344+
345+
PROC_SELF_FDINFO
346+
.get_or_try_init(|| {
347+
let (_, proc_stat) = proc()?;
348+
349+
let (proc_self, proc_self_stat) = proc_self()?;
350+
let oflags = OFlags::NOFOLLOW
351+
| OFlags::PATH
352+
| OFlags::DIRECTORY
353+
| OFlags::CLOEXEC
354+
| OFlags::NOCTTY
355+
| OFlags::NOATIME;
356+
357+
// Open "/proc/self/fdinfo".
358+
let proc_self_fdinfo = openat(&proc_self, zstr!("fdinfo"), oflags, Mode::empty())
359+
.map_err(|_err| io::Error::NOTSUP)?;
360+
let proc_self_fdinfo_stat = check_proc_entry(
361+
Kind::Fd,
362+
proc_self_fdinfo.as_fd(),
363+
Some(proc_stat),
364+
proc_self_stat.st_uid,
365+
proc_self_stat.st_gid,
366+
)
367+
.map_err(|_err| io::Error::NOTSUP)?;
368+
369+
Ok((proc_self_fdinfo, proc_self_fdinfo_stat))
370+
})
371+
.map(|(owned, stat)| (owned.as_fd(), stat))
372+
}
373+
374+
/// Returns a handle to a Linux `/proc/self/fdinfo/<fd>` file.
375+
///
376+
/// This ensures that `/proc/self/fdinfo/<fd>` is `procfs`, that nothing is
377+
/// mounted on top of it, and that it looks normal.
378+
///
379+
/// # References
380+
/// - [Linux]
381+
///
382+
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
383+
#[inline]
384+
pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: &Fd) -> io::Result<OwnedFd> {
385+
let fd = fd.as_fd();
386+
_proc_self_fdinfo(fd)
387+
}
388+
389+
fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
390+
let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?;
391+
let fd_str = DecInt::from_fd(&fd);
392+
open_and_check_file(proc_self_fdinfo, proc_self_fdinfo_stat, fd_str.as_z_str())
393+
}
394+
395+
/// Returns a handle to a Linux `/proc/self/pagemap` file.
396+
///
397+
/// This ensures that `/proc/self/pagemap` is `procfs`, that nothing is
398+
/// mounted on top of it, and that it looks normal.
399+
///
400+
/// # References
401+
/// - [Linux]
402+
/// - [Linux pagemap]
403+
///
404+
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
405+
/// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt
406+
#[inline]
407+
pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
408+
proc_self_file(zstr!("pagemap"))
409+
}
410+
411+
/// Returns a handle to a Linux `/proc/self/maps` file.
412+
///
413+
/// This ensures that `/proc/self/maps` is `procfs`, that nothing is
414+
/// mounted on top of it, and that it looks normal.
415+
///
416+
/// # References
417+
/// - [Linux]
418+
///
419+
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
420+
#[inline]
421+
pub fn proc_self_maps() -> io::Result<OwnedFd> {
422+
proc_self_file(zstr!("maps"))
423+
}
424+
425+
/// Open a file under `/proc/self`.
426+
fn proc_self_file(name: &ZStr) -> io::Result<OwnedFd> {
427+
let (proc_self, proc_self_stat) = proc_self()?;
428+
open_and_check_file(proc_self, proc_self_stat, name)
429+
}
430+
431+
/// Open a procfs file within in `dir` and check it for bind mounts.
432+
fn open_and_check_file(dir: BorrowedFd, dir_stat: &Stat, name: &ZStr) -> io::Result<OwnedFd> {
433+
let (_, proc_stat) = proc()?;
434+
435+
let oflags =
436+
OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY | OFlags::NOATIME;
437+
let file = openat(&dir, name, oflags, Mode::empty()).map_err(|_err| io::Error::NOTSUP)?;
438+
let file_stat = fstat(&file)?;
439+
440+
// Open a copy of the `dir` handle so that we can read from it
441+
// without modifying the current position of the original handle.
442+
let dot = openat(&dir, zstr!("."), oflags | OFlags::DIRECTORY, Mode::empty())
443+
.map_err(|_err| io::Error::NOTSUP)?;
444+
445+
// Confirm that we got the same inode.
446+
let dot_stat = fstat(&dot).map_err(|_err| io::Error::NOTSUP)?;
447+
if (dot_stat.st_dev, dot_stat.st_ino) != (dir_stat.st_dev, dir_stat.st_ino) {
448+
return Err(io::Error::NOTSUP);
449+
}
450+
451+
// `is_mountpoint` only works on directory mount points, not file mount
452+
// points. To detect file mount points, scan the parent directory to see
453+
// if we can find a regular file with an inode and name that matches the
454+
// file we just opened. If we can't find it, there could be a file bind
455+
// mount on top of the file we want.
456+
let dir = Dir::from(dot).map_err(|_err| io::Error::NOTSUP)?;
457+
for entry in dir {
458+
let entry = entry.map_err(|_err| io::Error::NOTSUP)?;
459+
if entry.ino() == file_stat.st_ino
460+
&& entry.file_type() == FileType::RegularFile
461+
&& entry.file_name() == name
462+
{
463+
// Ok, we found it. Proceed to check the file handle and succeed.
464+
let _ = check_proc_entry_with_stat(
465+
Kind::File,
466+
file.as_fd(),
467+
file_stat,
468+
Some(proc_stat),
469+
dir_stat.st_uid,
470+
dir_stat.st_gid,
471+
)?;
472+
473+
return Ok(file);
474+
}
475+
}
476+
477+
Err(io::Error::NOTSUP)
478+
}

tests/process/proc.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#[test]
2+
fn test_proc_funcs() {
3+
let _maps = rustix::io::proc_self_maps().unwrap();
4+
let _pagemap = rustix::io::proc_self_pagemap().unwrap();
5+
}

0 commit comments

Comments
 (0)