Skip to content

Commit cc7f4d9

Browse files
committed
merge #72 into openSUSE/libpathrs:main
Aleksa Sarai (8): capi: procfs: use open-enum to detect invalid enum values bindings: add support for PATHRS_PROC_ROOT tests: procfs: run some tests as root tests: procfs: add ProcRoot tests procfs: add support for ProcfsBase::ProcRoot procfs: create temporary handle for non-subset=pid subpaths tests: mntns: always set MS_SLAVE for tests utils: sysctl: prettify asserts LGTMs: cyphar
2 parents ce6226e + fa5c73c commit cc7f4d9

File tree

12 files changed

+383
-163
lines changed

12 files changed

+383
-163
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77
## [Unreleased] ##
88

99
### Added ###
10+
- procfs: add support for operating on files in the `/proc` root (or other
11+
processes) with `ProcfsBase::ProcRoot`.
12+
13+
While the cached file descriptor shouldn't leak into containers (container
14+
runtimes know to set `PR_SET_DUMPABLE`, and our cached file descriptor is
15+
`O_CLOEXEC`), I felt a little uncomfortable about having a global unmasked
16+
procfs handle sitting around in `libpathrs`. So, in order to avoid making a
17+
file descriptor leak by a `libpathrs` user catastrophic, `libpathrs` will
18+
always try to use a "limited" procfs handle as the global cached handle
19+
(which is much safer to leak into a container) and for operations on
20+
`ProcfsBase::ProcRoot`, a temporary new "unrestricted" procfs handle is
21+
created just for that operartion. This is more expensive, but it avoids a
22+
potential leak turning into a breakout or other nightmare scenario.
23+
1024
- python bindings: The `cffi` build script is now a little easier to use for
1125
distributions that want to build the python bindings at the same time as the
1226
main library. After compiling the library, set the `PATHRS_SRC_ROOT`
@@ -28,6 +42,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2842
filesystem configurations (POSIX ACLs, weird filesystem-specific mount
2943
options). (#71)
3044

45+
- capi: Passing invalid `pathrs_proc_base_t` values to `pathrs_proc_*` will now
46+
return an error rather than resulting in Undefined Behaviour™.
47+
3148
## [0.1.0] - 2024-09-14 ##
3249

3350
> 負けたくないことに理由って要る?

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ maintenance = { status = "experimental" }
3737
crate-type = ["rlib"]
3838

3939
[features]
40-
capi = ["dep:rand"]
40+
capi = ["dep:rand", "dep:open-enum"]
4141
# Only used for tests.
4242
_test_as_root = []
4343

@@ -53,6 +53,8 @@ itertools = "^0.13"
5353
lazy_static = "^1"
5454
libc = "^0.2"
5555
memchr = "^2"
56+
# MSRV(1.65): Update to >=0.4.1 which uses let_else. 0.4.0 was broken.
57+
open-enum = {version = "=0.3.0", optional = true }
5658
rand = { version = "^0.8", optional = true }
5759
rustix = { version = "^0.38", features = ["fs"] }
5860
thiserror = "^1"

contrib/bindings/python/pathrs/_pathrs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def _convert_mode(mode):
220220
return flags
221221

222222

223+
PROC_ROOT = libpathrs_so.PATHRS_PROC_ROOT
223224
PROC_SELF = libpathrs_so.PATHRS_PROC_SELF
224225
PROC_THREAD_SELF = libpathrs_so.PATHRS_PROC_THREAD_SELF
225226

go-pathrs/libpathrs_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ func pathrsHardlink(rootFd uintptr, path, target string) error {
195195
type pathrsProcBase C.pathrs_proc_base_t
196196

197197
const (
198+
pathrsProcRoot pathrsProcBase = C.PATHRS_PROC_ROOT
198199
pathrsProcSelf pathrsProcBase = C.PATHRS_PROC_SELF
199200
pathrsProcThreadSelf pathrsProcBase = C.PATHRS_PROC_THREAD_SELF
200201
)

go-pathrs/procfs_linux.go

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import (
2727
type ProcBase int
2828

2929
const (
30-
unimplementedProcBaseRoot ProcBase = iota
30+
// Use /proc. Note that this mode may be more expensive because we have to
31+
// take steps to try to avoid leaking unmasked procfs handles, so you
32+
// should use [ProcBaseSelf] if you can.
33+
ProcBaseRoot ProcBase = iota
3134
// Use /proc/self. For most programs, this is the standard choice.
3235
ProcBaseSelf
3336
// Use /proc/thread-self. In multi-threaded programs where one thread has
@@ -38,6 +41,8 @@ const (
3841

3942
func (b ProcBase) toPathrsBase() (pathrsProcBase, error) {
4043
switch b {
44+
case ProcBaseRoot:
45+
return pathrsProcRoot, nil
4146
case ProcBaseSelf:
4247
return pathrsProcSelf, nil
4348
case ProcBaseThreadSelf:
@@ -47,36 +52,65 @@ func (b ProcBase) toPathrsBase() (pathrsProcBase, error) {
4752
}
4853
}
4954

55+
func (b ProcBase) namePrefix() string {
56+
switch b {
57+
case ProcBaseRoot:
58+
return "/proc/"
59+
case ProcBaseSelf:
60+
return "/proc/self/"
61+
case ProcBaseThreadSelf:
62+
return "/proc/thread-self/"
63+
default:
64+
return "<invalid procfs base>/"
65+
}
66+
}
67+
5068
// ProcHandleCloser is a callback that needs to be called when you are done
51-
// operating on an *os.File fetched using ProcThreadSelfOpen.
69+
// operating on an *os.File fetched using [ProcThreadSelfOpen].
5270
type ProcHandleCloser func()
5371

54-
// TODO: Consider exporting procOpen once we have ProcBaseRoot.
55-
72+
// TODO: Should we expose procOpen?
5673
func procOpen(base ProcBase, path string, flags int) (*os.File, ProcHandleCloser, error) {
5774
pathrsBase, err := base.toPathrsBase()
5875
if err != nil {
5976
return nil, nil, err
6077
}
78+
namePrefix := base.namePrefix()
79+
6180
switch base {
62-
case ProcBaseSelf:
81+
case ProcBaseRoot, ProcBaseSelf:
6382
fd, err := pathrsProcOpen(pathrsBase, path, flags)
6483
if err != nil {
6584
return nil, nil, err
6685
}
67-
return os.NewFile(fd, "/proc/self/"+path), nil, nil
86+
return os.NewFile(fd, namePrefix+path), nil, nil
6887
case ProcBaseThreadSelf:
6988
runtime.LockOSThread()
7089
fd, err := pathrsProcOpen(pathrsBase, path, flags)
7190
if err != nil {
7291
runtime.UnlockOSThread()
7392
return nil, nil, err
7493
}
75-
return os.NewFile(fd, "/proc/thread-self/"+path), runtime.UnlockOSThread, nil
94+
return os.NewFile(fd, namePrefix+path), runtime.UnlockOSThread, nil
7695
}
7796
panic("unreachable")
7897
}
7998

99+
// ProcRootOpen safely opens a given path from inside /proc/.
100+
//
101+
// This function must only be used for accessing global information from procfs
102+
// (such as /proc/cpuinfo) or information about other processes (such as
103+
// /proc/1). Accessing your own process information should be done using
104+
// [ProcSelfOpen] or [ProcThreadSelfOpen].
105+
func ProcRootOpen(path string, flags int) (*os.File, error) {
106+
file, closer, err := procOpen(ProcBaseRoot, path, flags)
107+
if closer != nil {
108+
// should not happen
109+
panic("non-zero closer returned from procOpen(ProcBaseRoot)")
110+
}
111+
return file, err
112+
}
113+
80114
// ProcSelfOpen safely opens a given path from inside /proc/self/.
81115
//
82116
// This method is recommend for getting process information about the current
@@ -86,13 +120,14 @@ func procOpen(base ProcBase, path string, flags int) (*os.File, ProcHandleCloser
86120
//
87121
// For such non-heterogeneous processes, /proc/self may reference to a task
88122
// that has different state from the current goroutine and so it may be
89-
// preferable to use ProcThreadSelfOpen. The same is true if a user really
123+
// preferable to use [ProcThreadSelfOpen]. The same is true if a user really
90124
// wants to inspect the current OS thread's information (such as
91125
// /proc/thread-self/stack or /proc/thread-self/status which is always uniquely
92126
// per-thread).
93127
//
94-
// Unlike ProcThreadSelfOpen, this method does not involve locking the
95-
// goroutine to the current OS thread and so is simpler to use.
128+
// Unlike [ProcThreadSelfOpen], this method does not involve locking the
129+
// goroutine to the current OS thread and so is simpler to use and
130+
// theoretically has slightly less overhead.
96131
func ProcSelfOpen(path string, flags int) (*os.File, error) {
97132
file, closer, err := procOpen(ProcBaseSelf, path, flags)
98133
if closer != nil {
@@ -105,7 +140,7 @@ func ProcSelfOpen(path string, flags int) (*os.File, error) {
105140
// ProcThreadSelfOpen safely opens a given path from inside /proc/thread-self/.
106141
//
107142
// Most Go processes have heterogeneous threads (all threads have most of the
108-
// same kernel state such as CLONE_FS) and so ProcSelfOpen is preferable for
143+
// same kernel state such as CLONE_FS) and so [ProcSelfOpen] is preferable for
109144
// most users.
110145
//
111146
// For non-heterogeneous threads, or users that actually want thread-specific
@@ -129,7 +164,7 @@ func ProcThreadSelfOpen(path string, flags int) (*os.File, ProcHandleCloser, err
129164
//
130165
// This is effectively equivalent to doing a Proc*Open(O_PATH|O_NOFOLLOW) of
131166
// the path and then doing unix.Readlinkat(fd, ""), but with the benefit that
132-
// thread locking is not necessary for ProcBaseThreadSelf.
167+
// thread locking is not necessary for [ProcBaseThreadSelf].
133168
func ProcReadlink(base ProcBase, path string) (string, error) {
134169
pathrsBase, err := base.toPathrsBase()
135170
if err != nil {

include/pathrs.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@
4343
* pathrs_proc_*. This is necessary because /proc/thread-self is not present on
4444
* pre-3.17 kernels and so it may be necessary to emulate /proc/thread-self
4545
* access on those older kernels.
46-
*
47-
* NOTE: Currently, operating on /proc/... directly is not supported.
4846
*/
49-
typedef enum {
47+
enum pathrs_proc_base_t {
48+
/**
49+
* Use `/proc`. Note that this mode may be more expensive because we have
50+
* to take steps to try to avoid leaking unmasked procfs handles, so you
51+
* should use `PATHRS_PROC_SELF` if you can.
52+
*/
53+
PATHRS_PROC_ROOT = 1342308351,
5054
/**
5155
* Use /proc/self. For most programs, this is the standard choice.
5256
*/
@@ -61,7 +65,8 @@ typedef enum {
6165
* be killed (such as Go -- where you want to use runtime.LockOSThread).
6266
*/
6367
PATHRS_PROC_THREAD_SELF = 1051549215,
64-
} pathrs_proc_base_t;
68+
};
69+
typedef uint64_t pathrs_proc_base_t;
6570

6671
/**
6772
* Attempts to represent a Rust Error type in C. This structure must be freed

src/capi/procfs.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,33 @@
1919

2020
use crate::{
2121
capi::{ret::IntoCReturn, utils},
22-
error::Error,
22+
error::{Error, ErrorImpl},
2323
flags::OpenFlags,
2424
procfs::{ProcfsBase, GLOBAL_PROCFS_HANDLE},
2525
};
2626

27-
use std::os::unix::io::{OwnedFd, RawFd};
27+
use std::{
28+
convert::TryFrom,
29+
os::unix::io::{OwnedFd, RawFd},
30+
};
2831

2932
use libc::{c_char, c_int, size_t};
33+
use open_enum::open_enum;
3034

3135
/// Indicate what base directory should be used when doing operations with
3236
/// pathrs_proc_*. This is necessary because /proc/thread-self is not present on
3337
/// pre-3.17 kernels and so it may be necessary to emulate /proc/thread-self
3438
/// access on those older kernels.
35-
///
36-
/// NOTE: Currently, operating on /proc/... directly is not supported.
37-
#[repr(C)]
39+
#[open_enum]
40+
#[repr(u64)]
3841
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
3942
#[allow(non_camel_case_types, dead_code)]
4043
pub enum CProcfsBase {
41-
//PATHRS_PROC_ROOT, // TODO
44+
/// Use `/proc`. Note that this mode may be more expensive because we have
45+
/// to take steps to try to avoid leaking unmasked procfs handles, so you
46+
/// should use `PATHRS_PROC_SELF` if you can.
47+
PATHRS_PROC_ROOT = 0x5001_FFFF,
48+
4249
/// Use /proc/self. For most programs, this is the standard choice.
4350
PATHRS_PROC_SELF = 0x091D_5E1F,
4451

@@ -52,11 +59,19 @@ pub enum CProcfsBase {
5259
PATHRS_PROC_THREAD_SELF = 0x3EAD_5E1F,
5360
}
5461

55-
impl From<CProcfsBase> for ProcfsBase {
56-
fn from(c_base: CProcfsBase) -> Self {
62+
impl TryFrom<CProcfsBase> for ProcfsBase {
63+
type Error = Error;
64+
65+
fn try_from(c_base: CProcfsBase) -> Result<Self, Self::Error> {
5766
match c_base {
58-
CProcfsBase::PATHRS_PROC_SELF => ProcfsBase::ProcSelf,
59-
CProcfsBase::PATHRS_PROC_THREAD_SELF => ProcfsBase::ProcThreadSelf,
67+
CProcfsBase::PATHRS_PROC_ROOT => Ok(ProcfsBase::ProcRoot),
68+
CProcfsBase::PATHRS_PROC_SELF => Ok(ProcfsBase::ProcSelf),
69+
CProcfsBase::PATHRS_PROC_THREAD_SELF => Ok(ProcfsBase::ProcThreadSelf),
70+
_ => Err(ErrorImpl::InvalidArgument {
71+
name: "procfs base".into(),
72+
description: "the procfs base must be one of the PATHRS_PROC_* values".into(),
73+
}
74+
.into()),
6075
}
6176
}
6277
}
@@ -65,6 +80,7 @@ impl From<CProcfsBase> for ProcfsBase {
6580
impl From<ProcfsBase> for CProcfsBase {
6681
fn from(base: ProcfsBase) -> Self {
6782
match base {
83+
ProcfsBase::ProcRoot => CProcfsBase::PATHRS_PROC_ROOT,
6884
ProcfsBase::ProcSelf => CProcfsBase::PATHRS_PROC_SELF,
6985
ProcfsBase::ProcThreadSelf => CProcfsBase::PATHRS_PROC_THREAD_SELF,
7086
}
@@ -114,12 +130,13 @@ pub unsafe extern "C" fn pathrs_proc_open(
114130
flags: c_int,
115131
) -> RawFd {
116132
|| -> Result<_, Error> {
133+
let base = base.try_into()?;
117134
let path = unsafe { utils::parse_path(path) }?; // SAFETY: C caller guarantees path is safe.
118135
let oflags = OpenFlags::from_bits_retain(flags);
119136

120137
match oflags.contains(OpenFlags::O_NOFOLLOW) {
121-
true => GLOBAL_PROCFS_HANDLE.open(base.into(), path, oflags),
122-
false => GLOBAL_PROCFS_HANDLE.open_follow(base.into(), path, oflags),
138+
true => GLOBAL_PROCFS_HANDLE.open(base, path, oflags),
139+
false => GLOBAL_PROCFS_HANDLE.open_follow(base, path, oflags),
123140
}
124141
}()
125142
.map(OwnedFd::from)
@@ -170,8 +187,9 @@ pub unsafe extern "C" fn pathrs_proc_readlink(
170187
linkbuf_size: size_t,
171188
) -> c_int {
172189
|| -> Result<_, Error> {
190+
let base = base.try_into()?;
173191
let path = unsafe { utils::parse_path(path) }?; // SAFETY: C caller guarantees path is safe.
174-
let link_target = GLOBAL_PROCFS_HANDLE.readlink(base.into(), path)?;
192+
let link_target = GLOBAL_PROCFS_HANDLE.readlink(base, path)?;
175193
// SAFETY: C caller guarantees buffer is at least linkbuf_size and can
176194
// be written to.
177195
unsafe { utils::copy_path_into_buffer(link_target, linkbuf, linkbuf_size) }

0 commit comments

Comments
 (0)