Expand description
libpathrs provides a series of primitives for Linux programs to safely handle path operations inside an untrusted directory tree.
The idea is that a Root
handle is like a handle for resolution inside a
chroot(2)
, with Handle
being an O_PATH
descriptor which you can
“upgrade” to a proper File
. However this library acts far more
efficiently than spawning a new process and doing a full chroot(2)
for
every operation.
§Example
The recommended usage of libpathrs looks something like this:
let (root_path, unsafe_path) = ("/path/to/root", "/etc/passwd");
// Get a root handle for resolution.
let root = Root::open(root_path)?;
// Resolve the path.
let handle = root.resolve(unsafe_path)?;
// Upgrade the handle to a full std::fs::File.
let file = handle.reopen(OpenFlags::O_RDONLY)?;
// Or, in one line:
let file = root.resolve(unsafe_path)?
.reopen(OpenFlags::O_RDONLY)?;
§C API
In order to ensure the maximum possible number of people can make us of this library to increase the overall security of Linux tooling, it is written in Rust (to be memory-safe) and produces C dylibs for usage with any language that supports C-based FFI. To further help expand how many folks can use libpathrs, libpathrs’s MSRV is Rust 1.63, to allow us to build on more stable operating systems (such as Debian Buster, which provides Rust 1.63).
A C example corresponding to the above Rust code would look like:
#include <pathrs.h>
int get_my_fd(void)
{
const char *root_path = "/path/to/root";
const char *unsafe_path = "/etc/passwd";
int liberr = 0;
int root = -EBADF,
handle = -EBADF,
fd = -EBADF;
root = pathrs_root_open(root_path);
if (root < 0) {
liberr = root;
goto err;
}
handle = pathrs_resolve(root, unsafe_path);
if (handle < 0) {
liberr = handle;
goto err;
}
fd = pathrs_reopen(handle, O_RDONLY);
if (fd < 0) {
liberr = fd;
goto err;
}
err:
if (liberr < 0) {
pathrs_error_t *error = pathrs_errorinfo(liberr);
fprintf(stderr, "Uh-oh: %s (errno=%d)\n", error->description, error->saved_errno);
pathrs_errorinfo_free(error);
}
close(root);
close(handle);
return fd;
}
§Kernel Support
libpathrs is designed to only work with Linux, as it uses several Linux-only APIs.
libpathrs was designed alongside openat2(2)
(available since Linux 5.6)
and dynamically tries to use the latest kernel features to provide the
maximum possible protection against racing attackers. However, it also
provides support for older kernel versions (in theory up to Linux
2.6.39 but we do not currently test this) by emulating newer kernel features
in userspace.
However, we strongly recommend you use at least Linux 5.8 to get a reasonable amount of protection against various attacks, and ideally at least Linux 6.8 to make use of all of the protections we have implemented. See the following table for what kernel features we optionally support and what they are used for.
Feature | Minimum Kernel Version | Description | Fallback |
---|---|---|---|
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. |
/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. |
New Mount API | Linux 5.2 (2019-07-07) | Used to create a private procfs handle when operating on /proc (with fsopen(2) or open_tree(2) ). | Open a regular handle to /proc . This can lead to certain race attacks if the attacker can dynamically create mounts. |
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. | There is no fallback. Not using this protection can lead to fairly trivial attacks if an attacker can configure your mount table. |
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). |
For more information about the work behind openat2(2)
, you can read the
following LWN articles (note that the merged version of openat2(2)
is
different to the version described by LWN):
Modules§
- Error types for libpathrs.
- Bit-flags for modifying the behaviour of libpathrs.
- Helpers to operate on
procfs
safely.
Structs§
- A handle to an existing inode within a
Root
. - Borrowed version of
Handle
. - A handle to the root of a directory tree.
- Borrowed version of
Root
.
Enums§
- An inode type to be created with
Root::create
.