Skip to content

Commit 69319a3

Browse files
authored
Fix procfs on /proc/self with user namespaces (#859)
* Fix procfs on /proc/self with user namespaces Use `readlink` on the /proc/self symlink instead of `getpid`, so that we get the pid relative to the procfs's pid namespace rather than the process' pid namespace. Fixes #854. * Avoid using `Vec`. * Use `RawDir` to avoid allocationo.
1 parent 5f0db52 commit 69319a3

4 files changed

Lines changed: 92 additions & 45 deletions

File tree

src/backend/libc/fs/syscalls.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! libc syscalls supporting `rustix::fs`.
22
33
use crate::backend::c;
4-
#[cfg(any(apple, linux_kernel, feature = "alloc"))]
4+
#[cfg(any(
5+
apple,
6+
linux_kernel,
7+
feature = "alloc",
8+
all(linux_kernel, feature = "procfs")
9+
))]
510
use crate::backend::conv::ret_usize;
611
use crate::backend::conv::{borrowed_fd, c_str, ret, ret_c_int, ret_off_t, ret_owned_fd};
712
use crate::fd::{BorrowedFd, OwnedFd};
@@ -258,7 +263,10 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
258263
}
259264
}
260265

261-
#[cfg(all(feature = "alloc", not(target_os = "redox")))]
266+
#[cfg(all(
267+
any(feature = "alloc", all(linux_kernel, feature = "procfs")),
268+
not(target_os = "redox")
269+
))]
262270
#[inline]
263271
pub(crate) fn readlinkat(
264272
dirfd: BorrowedFd<'_>,

src/backend/linux_raw/fs/syscalls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ pub(crate) fn readlink(path: &CStr, buf: &mut [u8]) -> io::Result<usize> {
951951
}
952952
}
953953

954-
#[cfg(feature = "alloc")]
954+
#[cfg(any(feature = "alloc", all(linux_kernel, feature = "procfs")))]
955955
#[inline]
956956
pub(crate) fn readlinkat(
957957
dirfd: BorrowedFd<'_>,

src/fs/raw_dir.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
4343
/// ```
4444
/// # use std::mem::MaybeUninit;
4545
/// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
46+
/// # use rustix::cstr;
4647
///
4748
/// let fd = openat(
4849
/// CWD,
49-
/// ".",
50+
/// cstr!("."),
5051
/// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
5152
/// Mode::empty(),
5253
/// )
@@ -65,10 +66,11 @@ impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
6566
/// ```
6667
/// # use std::mem::MaybeUninit;
6768
/// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
69+
/// # use rustix::cstr;
6870
///
6971
/// let fd = openat(
7072
/// CWD,
71-
/// ".",
73+
/// cstr!("."),
7274
/// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
7375
/// Mode::empty(),
7476
/// )
@@ -92,10 +94,11 @@ impl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
9294
/// # use std::mem::MaybeUninit;
9395
/// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
9496
/// # use rustix::io::Errno;
97+
/// # use rustix::cstr;
9598
///
9699
/// let fd = openat(
97100
/// CWD,
98-
/// ".",
101+
/// cstr!("."),
99102
/// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
100103
/// Mode::empty(),
101104
/// )

src/procfs.rs

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,19 @@
1818
//! namespace. So with the checking here, they may fail, but they won't be able
1919
//! to succeed with bogus results.
2020
21-
use crate::backend::pid::syscalls::getpid;
2221
use crate::fd::{AsFd, BorrowedFd, OwnedFd};
22+
use crate::ffi::CStr;
2323
use crate::fs::{
24-
fstat, fstatfs, major, openat, renameat, FileType, FsWord, Mode, OFlags, Stat, CWD,
24+
fstat, fstatfs, major, openat, renameat, FileType, FsWord, Mode, OFlags, RawDir, Stat, CWD,
2525
PROC_SUPER_MAGIC,
2626
};
2727
use crate::io;
2828
use crate::path::DecInt;
2929
#[cfg(feature = "rustc-dep-of-std")]
3030
use core::lazy::OnceCell;
31+
use core::mem::MaybeUninit;
3132
#[cfg(not(feature = "rustc-dep-of-std"))]
3233
use once_cell::sync::OnceCell;
33-
#[cfg(feature = "alloc")]
34-
use {crate::ffi::CStr, crate::fs::Dir};
3534

3635
/// Linux's procfs always uses inode 1 for its root directory.
3736
const PROC_ROOT_INO: u64 = 1;
@@ -42,8 +41,8 @@ enum Kind {
4241
Proc,
4342
Pid,
4443
Fd,
45-
#[cfg(feature = "alloc")]
4644
File,
45+
Symlink,
4746
}
4847

4948
/// Check a subdirectory of "/proc" for anomalies.
@@ -69,16 +68,23 @@ fn check_proc_entry_with_stat(
6968
match kind {
7069
Kind::Proc => check_proc_root(entry, &entry_stat)?,
7170
Kind::Pid | Kind::Fd => check_proc_subdir(entry, &entry_stat, proc_stat)?,
72-
#[cfg(feature = "alloc")]
7371
Kind::File => check_proc_file(&entry_stat, proc_stat)?,
72+
Kind::Symlink => check_proc_symlink(&entry_stat, proc_stat)?,
7473
}
7574

7675
// "/proc" directories are typically mounted r-xr-xr-x.
7776
// "/proc/self/fd" is r-x------. Allow them to have fewer permissions, but
7877
// not more.
79-
let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 };
80-
if entry_stat.st_mode & 0o777 & !expected_mode != 0 {
81-
return Err(io::Errno::NOTSUP);
78+
match kind {
79+
Kind::Symlink => {
80+
// On Linux, symlinks don't have their own permissions.
81+
}
82+
_ => {
83+
let expected_mode = if let Kind::Fd = kind { 0o500 } else { 0o555 };
84+
if entry_stat.st_mode & 0o777 & !expected_mode != 0 {
85+
return Err(io::Errno::NOTSUP);
86+
}
87+
}
8288
}
8389

8490
match kind {
@@ -97,14 +103,20 @@ fn check_proc_entry_with_stat(
97103
return Err(io::Errno::NOTSUP);
98104
}
99105
}
100-
#[cfg(feature = "alloc")]
101106
Kind::File => {
102107
// Check that files in procfs don't have extraneous hard links to
103108
// them (which might indicate hard links to other things).
104109
if entry_stat.st_nlink != 1 {
105110
return Err(io::Errno::NOTSUP);
106111
}
107112
}
113+
Kind::Symlink => {
114+
// Check that symlinks in procfs don't have extraneous hard links
115+
// to them (which might indicate hard links to other things).
116+
if entry_stat.st_nlink != 1 {
117+
return Err(io::Errno::NOTSUP);
118+
}
119+
}
108120
}
109121

110122
Ok(entry_stat)
@@ -153,7 +165,6 @@ fn check_proc_subdir(
153165
Ok(())
154166
}
155167

156-
#[cfg(feature = "alloc")]
157168
fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
158169
// Check that we have a regular file.
159170
if FileType::from_raw_mode(stat.st_mode) != FileType::RegularFile {
@@ -165,6 +176,17 @@ fn check_proc_file(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
165176
Ok(())
166177
}
167178

179+
fn check_proc_symlink(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
180+
// Check that we have a symbolic link.
181+
if FileType::from_raw_mode(stat.st_mode) != FileType::Symlink {
182+
return Err(io::Errno::NOTSUP);
183+
}
184+
185+
check_proc_nonroot(stat, proc_stat)?;
186+
187+
Ok(())
188+
}
189+
168190
fn check_proc_nonroot(stat: &Stat, proc_stat: Option<&Stat>) -> io::Result<()> {
169191
// Check that we haven't been linked back to the root of "/proc".
170192
if stat.st_ino == PROC_ROOT_INO {
@@ -245,6 +267,7 @@ fn proc() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
245267
/// - [Linux]
246268
///
247269
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
270+
#[allow(unsafe_code)]
248271
fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
249272
static PROC_SELF: StaticFd = StaticFd::new();
250273

@@ -253,11 +276,21 @@ fn proc_self() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
253276
.get_or_try_init(|| {
254277
let (proc, proc_stat) = proc()?;
255278

256-
let pid = getpid();
279+
// `getpid` would return our pid in our own pid namespace, so
280+
// instead use `readlink` on the `self` symlink to learn our pid in
281+
// the procfs namespace.
282+
let self_symlink = open_and_check_file(proc, proc_stat, cstr!("self"), Kind::Symlink)?;
283+
let mut buf = [MaybeUninit::<u8>::uninit(); 20];
284+
let len = crate::backend::fs::syscalls::readlinkat(
285+
self_symlink.as_fd(),
286+
cstr!(""),
287+
&mut buf,
288+
)?;
289+
let pid: &[u8] = unsafe { core::mem::transmute(&buf[..len]) };
257290

258291
// Open "/proc/self". Use our pid to compute the name rather than
259292
// literally using "self", as "self" is a symlink.
260-
let proc_self = proc_opendirat(proc, DecInt::new(pid.as_raw_nonzero().get()))?;
293+
let proc_self = proc_opendirat(proc, pid)?;
261294
let proc_self_stat = check_proc_entry(Kind::Pid, proc_self.as_fd(), Some(proc_stat))
262295
.map_err(|_err| io::Errno::NOTSUP)?;
263296

@@ -314,7 +347,6 @@ fn new_static_fd(fd: OwnedFd, stat: Stat) -> (OwnedFd, Stat) {
314347
/// - [Linux]
315348
///
316349
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
317-
#[cfg(feature = "alloc")]
318350
fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
319351
static PROC_SELF_FDINFO: StaticFd = StaticFd::new();
320352

@@ -344,18 +376,21 @@ fn proc_self_fdinfo() -> io::Result<(BorrowedFd<'static>, &'static Stat)> {
344376
/// - [Linux]
345377
///
346378
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
347-
#[cfg(feature = "alloc")]
348379
#[inline]
349380
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
350381
pub fn proc_self_fdinfo_fd<Fd: AsFd>(fd: Fd) -> io::Result<OwnedFd> {
351382
_proc_self_fdinfo(fd.as_fd())
352383
}
353384

354-
#[cfg(feature = "alloc")]
355385
fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
356386
let (proc_self_fdinfo, proc_self_fdinfo_stat) = proc_self_fdinfo()?;
357387
let fd_str = DecInt::from_fd(fd);
358-
open_and_check_file(proc_self_fdinfo, proc_self_fdinfo_stat, fd_str.as_c_str())
388+
open_and_check_file(
389+
proc_self_fdinfo,
390+
proc_self_fdinfo_stat,
391+
fd_str.as_c_str(),
392+
Kind::File,
393+
)
359394
}
360395

361396
/// Returns a handle to a Linux `/proc/self/pagemap` file.
@@ -369,7 +404,6 @@ fn _proc_self_fdinfo(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
369404
///
370405
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
371406
/// [Linux pagemap]: https://www.kernel.org/doc/Documentation/vm/pagemap.txt
372-
#[cfg(feature = "alloc")]
373407
#[inline]
374408
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
375409
pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
@@ -385,7 +419,6 @@ pub fn proc_self_pagemap() -> io::Result<OwnedFd> {
385419
/// - [Linux]
386420
///
387421
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
388-
#[cfg(feature = "alloc")]
389422
#[inline]
390423
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
391424
pub fn proc_self_maps() -> io::Result<OwnedFd> {
@@ -401,23 +434,25 @@ pub fn proc_self_maps() -> io::Result<OwnedFd> {
401434
/// - [Linux]
402435
///
403436
/// [Linux]: https://man7.org/linux/man-pages/man5/proc.5.html
404-
#[cfg(feature = "alloc")]
405437
#[inline]
406438
#[cfg_attr(doc_cfg, doc(cfg(feature = "procfs")))]
407439
pub fn proc_self_status() -> io::Result<OwnedFd> {
408440
proc_self_file(cstr!("status"))
409441
}
410442

411443
/// Open a file under `/proc/self`.
412-
#[cfg(feature = "alloc")]
413444
fn proc_self_file(name: &CStr) -> io::Result<OwnedFd> {
414445
let (proc_self, proc_self_stat) = proc_self()?;
415-
open_and_check_file(proc_self, proc_self_stat, name)
446+
open_and_check_file(proc_self, proc_self_stat, name, Kind::File)
416447
}
417448

418449
/// Open a procfs file within in `dir` and check it for bind mounts.
419-
#[cfg(feature = "alloc")]
420-
fn open_and_check_file(dir: BorrowedFd<'_>, dir_stat: &Stat, name: &CStr) -> io::Result<OwnedFd> {
450+
fn open_and_check_file(
451+
dir: BorrowedFd<'_>,
452+
dir_stat: &Stat,
453+
name: &CStr,
454+
kind: Kind,
455+
) -> io::Result<OwnedFd> {
421456
let (_, proc_stat) = proc()?;
422457

423458
// Don't use `NOATIME`, because it [requires us to own the file], and when
@@ -426,7 +461,11 @@ fn open_and_check_file(dir: BorrowedFd<'_>, dir_stat: &Stat, name: &CStr) -> io:
426461
//
427462
// [requires us to own the file]: https://man7.org/linux/man-pages/man2/openat.2.html
428463
// [to root:root]: https://man7.org/linux/man-pages/man5/proc.5.html
429-
let oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY;
464+
let mut oflags = OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NOCTTY;
465+
if let Kind::Symlink = kind {
466+
// Open symlinks with `O_PATH`.
467+
oflags |= OFlags::PATH;
468+
}
430469
let file = openat(dir, name, oflags, Mode::empty()).map_err(|_err| io::Errno::NOTSUP)?;
431470
let file_stat = fstat(&file)?;
432471

@@ -436,32 +475,29 @@ fn open_and_check_file(dir: BorrowedFd<'_>, dir_stat: &Stat, name: &CStr) -> io:
436475
// we just opened. If we can't find it, there could be a file bind mount on
437476
// top of the file we want.
438477
//
439-
// As we scan, we also check for ".", to make sure it's the same directory
440-
// as our original directory, to detect mount points, since
441-
// `Dir::read_from` reopens ".".
442-
//
443478
// TODO: With Linux 5.8 we might be able to use `statx` and
444479
// `STATX_ATTR_MOUNT_ROOT` to detect mountpoints directly instead of doing
445480
// this scanning.
446-
let dir = Dir::read_from(dir).map_err(|_err| io::Errno::NOTSUP)?;
447481

448-
// Confirm that we got the same inode.
449-
let dot_stat = dir.stat().map_err(|_err| io::Errno::NOTSUP)?;
450-
if (dot_stat.st_dev, dot_stat.st_ino) != (dir_stat.st_dev, dir_stat.st_ino) {
451-
return Err(io::Errno::NOTSUP);
452-
}
482+
let expected_type = match kind {
483+
Kind::File => FileType::RegularFile,
484+
Kind::Symlink => FileType::Symlink,
485+
_ => unreachable!(),
486+
};
453487

454488
let mut found_file = false;
455489
let mut found_dot = false;
456-
for entry in dir {
490+
491+
let mut buf = [MaybeUninit::uninit(); 2048];
492+
let mut iter = RawDir::new(dir, &mut buf);
493+
while let Some(entry) = iter.next() {
457494
let entry = entry.map_err(|_err| io::Errno::NOTSUP)?;
458495
if entry.ino() == file_stat.st_ino
459-
&& entry.file_type() == FileType::RegularFile
496+
&& entry.file_type() == expected_type
460497
&& entry.file_name() == name
461498
{
462499
// We found the file. Proceed to check the file handle.
463-
let _ =
464-
check_proc_entry_with_stat(Kind::File, file.as_fd(), file_stat, Some(proc_stat))?;
500+
let _ = check_proc_entry_with_stat(kind, file.as_fd(), file_stat, Some(proc_stat))?;
465501

466502
found_file = true;
467503
} else if entry.ino() == dir_stat.st_ino

0 commit comments

Comments
 (0)