pathrs/lib.rs
1// SPDX-License-Identifier: MPL-2.0 OR LGPL-3.0-or-later
2/*
3 * libpathrs: safe path resolution on Linux
4 * Copyright (C) 2019-2025 SUSE LLC
5 * Copyright (C) 2026 Aleksa Sarai <cyphar@cyphar.com>
6 *
7 * == MPL-2.0 ==
8 *
9 * This Source Code Form is subject to the terms of the Mozilla Public
10 * License, v. 2.0. If a copy of the MPL was not distributed with this
11 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
12 *
13 * Alternatively, this Source Code Form may also (at your option) be used
14 * under the terms of the GNU Lesser General Public License Version 3, as
15 * described below:
16 *
17 * == LGPL-3.0-or-later ==
18 *
19 * This program is free software: you can redistribute it and/or modify it
20 * under the terms of the GNU Lesser General Public License as published by
21 * the Free Software Foundation, either version 3 of the License, or (at
22 * your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
27 * Public License for more details.
28 *
29 * You should have received a copy of the GNU Lesser General Public License
30 * along with this program. If not, see <https://www.gnu.org/licenses/>.
31 */
32
33//! libpathrs provides a series of primitives for Linux programs to safely
34//! handle path operations inside an untrusted directory tree.
35//!
36//! The idea is that a [`Root`] handle is like a handle for resolution inside a
37//! [`chroot(2)`], with [`Handle`] being an `O_PATH` descriptor which you can
38//! "upgrade" to a proper [`File`]. However this library acts far more
39//! efficiently than spawning a new process and doing a full [`chroot(2)`] for
40//! every operation.
41//!
42//! # Example
43//!
44//! The recommended usage of libpathrs looks something like this:
45//!
46//! ```
47//! # use pathrs::{error::Error, flags::OpenFlags, Root};
48//! # fn main() -> Result<(), Error> {
49//! let (root_path, unsafe_path) = ("/path/to/root", "/etc/passwd");
50//! # let root_path = "/";
51//! // Get a root handle for resolution.
52//! let root = Root::open(root_path)?;
53//! // Resolve the path.
54//! let handle = root.resolve(unsafe_path)?;
55//! // Upgrade the handle to a full std::fs::File.
56//! let file = handle.reopen(OpenFlags::O_RDONLY)?;
57//!
58//! // Or, in one line:
59//! let file = root.resolve(unsafe_path)?
60//! .reopen(OpenFlags::O_RDONLY)?;
61//! # Ok(())
62//! # }
63//! ```
64//!
65//! # C API
66//!
67//! In order to ensure the maximum possible number of people can make us of this
68//! library to increase the overall security of Linux tooling, it is written in
69//! Rust (to be memory-safe) and produces C dylibs for usage with any language
70//! that supports C-based FFI. To further help expand how many folks can use
71//! libpathrs, libpathrs's MSRV is Rust 1.63, to allow us to build on more
72//! stable operating systems (such as Debian Buster, which provides Rust 1.63).
73//!
74//! A C example corresponding to the above Rust code would look like:
75//!
76//! ```c
77//! #include <pathrs.h>
78//!
79//! int get_my_fd(void)
80//! {
81//! const char *root_path = "/path/to/root";
82//! const char *unsafe_path = "/etc/passwd";
83//!
84//! int liberr = 0;
85//! int root = -EBADF,
86//! handle = -EBADF,
87//! fd = -EBADF;
88//!
89//! root = pathrs_open_root(root_path);
90//! if (IS_PATHRS_ERR(root)) {
91//! liberr = root;
92//! goto err;
93//! }
94//!
95//! handle = pathrs_inroot_resolve(root, unsafe_path);
96//! if (IS_PATHRS_ERR(handle)) {
97//! liberr = handle;
98//! goto err;
99//! }
100//!
101//! fd = pathrs_reopen(handle, O_RDONLY);
102//! if (IS_PATHRS_ERR(fd)) {
103//! liberr = fd;
104//! goto err;
105//! }
106//!
107//! err:
108//! if (IS_PATHRS_ERR(liberr)) {
109//! pathrs_error_t *error = pathrs_errorinfo(liberr);
110//! fprintf(stderr, "Uh-oh: %s (errno=%d)\n", error->description, error->saved_errno);
111//! pathrs_errorinfo_free(error);
112//! }
113//! close(root);
114//! close(handle);
115//! return fd;
116//! }
117//! ```
118//!
119//! # Kernel Support
120//!
121//! libpathrs is designed to only work with Linux, as it uses several Linux-only
122//! APIs.
123//!
124//! libpathrs was designed alongside [`openat2(2)`] (available since Linux 5.6)
125//! and dynamically tries to use the latest kernel features to provide the
126//! maximum possible protection against racing attackers. However, it also
127//! provides support for older kernel versions (in theory up to Linux
128//! 2.6.39 but we do not currently test this) by emulating newer kernel features
129//! in userspace.
130//!
131//! However, we strongly recommend you use at least Linux 5.6 to get a
132//! reasonable amount of protection against various attacks, and ideally at
133//! least Linux 6.8 to make use of all of the protections we have implemented.
134//! See the following table for what kernel features we optionally support and
135//! what they are used for.
136//!
137//! | Feature | Minimum Kernel Version | Description | Fallback |
138//! | --------------------- | ----------------------- | ----------- | -------- |
139//! | `/proc/thread-self` | Linux 3.17 (2014-10-05) | Used when operating on the current thread's `/proc` directory for use with `PATHRS_PROC_THREAD_SELF`. | `/proc/self/task/$tid` is used, but this might not be available in some edge cases so `/proc/self` is used as a final fallback. |
140//! | [`open_tree(2)`] | Linux 5.2 (2018-07-07) | Used to create a private procfs handle when operating on `/proc` (this is a copy of the host `/proc` -- in most cases this will also strip any overmounts). Requires `CAP_SYS_ADMIN` privileges. | Open a regular handle to `/proc`. This can lead to certain race attacks if the attacker can dynamically create mounts. |
141//! | [`fsopen(2)`] | Linux 5.2 (2019-07-07) | Used to create a private procfs handle when operating on `/proc` (with a completely fresh copy of `/proc` -- in some cases this operation will fail if there are locked overmounts on top of `/proc`). Requires `CAP_SYS_ADMIN` privileges. | Try to use [`open_tree(2)`] instead -- in the case of errors due to locked overmounts, [`open_tree(2)`] will be used to create a recursive copy that preserves the overmounts. This means that an attacker would not be able to actively change the mounts on top of `/proc` but there might be some overmounts that libpathrs will detect (and reject). |
142//! | [`openat2(2)`] | Linux 5.6 (2020-03-29) | In-kernel restrictions of path lookup. This is used extensively by `libpathrs` to safely do path lookups. | Userspace emulated path lookups. |
143//! | `subset=pid` | Linux 5.8 (2020-08-02) | Allows for a `procfs` handle created with [`fsopen(2)`][] to not contain any global procfs files that would be dangerous for an attacker to write to. Detached `procfs` mounts with `subset=pid` are deemed safe(r) to leak into containers and so libpathrs will internally cache `subset=pid` [`ProcfsHandle`]s. | libpathrs's [`ProcfsHandle`]s will have global files and thus libpathrs will not cache a copy of the file descriptor for each operation (possibly causing substantially higher syscall usage as a result -- our testing found that this can have a performance impact in some cases). |
144//! | `STATX_MNT_ID` | Linux 5.8 (2020-08-02) | Used to verify whether there are bind-mounts on top of `/proc` that could result in insecure operations (on systems with `fsopen(2)` or `open_tree(2)` this protection is somewhat redundant for privileged programs -- those kinds of `procfs` handles will typically not have overmounts.) | Parse the `/proc/thread-self/fdinfo/$fd` directly -- for systems with `openat2(2)`, this is guaranteed to be safe against attacks. For systems without `openat2(2)`, we have to fallback to unsafe opens that could be fooled by bind-mounts -- however, we believe that exploitation of this would be difficult in practice (even with an attacker that has the persistent ability to mount to arbitrary paths) due to the way we verify `procfs` accesses. |
145//! | `STATX_MNT_ID_UNIQUE` | Linux 6.8 (2024-03-10) | Used for the same reason as `STATX_MNT_ID`, but allows us to protect against mount ID recycling. This is effectively a safer version of `STATX_MNT_ID`. | `STATX_MNT_ID` is used (see the `STATX_MNT_ID` fallback if it's not available either). |
146//!
147//! For more information about the work behind `openat2(2)`, you can read the
148//! following LWN articles (note that the merged version of `openat2(2)` is
149//! different to the version described by LWN):
150//!
151//! * [New AT_ flags for restricting pathname lookup][lwn-atflags]
152//! * [Restricting path name lookup with openat2()][lwn-openat2]
153//!
154//! [`openat2(2)`]: https://www.man7.org/linux/man-pages/man2/openat2.2.html
155//! [lwn-atflags]: https://lwn.net/Articles/767547/
156//! [lwn-openat2]: https://lwn.net/Articles/796868/
157//! [`File`]: std::fs::File
158//! [`chroot(2)`]: http://man7.org/linux/man-pages/man2/chroot.2.html
159//! [`open_tree(2)`]: https://github.com/brauner/man-pages-md/blob/main/open_tree.md
160//! [`fsopen(2)`]: https://github.com/brauner/man-pages-md/blob/main/fsopen.md
161//! [`ProcfsHandle`]: crate::procfs::ProcfsHandle
162
163// libpathrs only supports Linux at the moment.
164#![cfg(target_os = "linux")]
165#![deny(rustdoc::broken_intra_doc_links)]
166#![deny(clippy::all)]
167#![deny(missing_debug_implementations)]
168// We use this the coverage_attribute when doing coverage runs.
169// <https://github.com/rust-lang/rust/issues/84605>
170#![cfg_attr(coverage, feature(coverage_attribute))]
171
172// `Handle` implementation.
173mod handle;
174#[doc(inline)]
175pub use handle::*;
176
177// `Root` implementation.
178mod root;
179#[doc(inline)]
180pub use root::*;
181
182pub mod error;
183pub mod flags;
184pub mod procfs;
185
186// Resolver backend implementations.
187mod resolvers;
188
189// C API.
190#[cfg(feature = "capi")]
191mod capi;
192
193// Internally used helpers.
194mod syscalls;
195mod utils;
196
197// Library tests.
198#[cfg(test)]
199mod tests;