priv_sep

Crates.iopriv_sep
lib.rspriv_sep
version3.0.0-alpha.4.0
created_at2023-07-25 06:19:55.703818+00
updated_at2026-01-11 01:24:06.719083+00
descriptionFFI for chroot(2), pledge(2), setgroups(2), setresgid(2), setresuid(2), and unveil(2).
homepage
repositoryhttps://git.philomathiclife.com/repos/priv_sep/
max_upload_size
id925219
size251,116
philomathic_life (zacknewman)

documentation

https://docs.rs/priv_sep/latest/priv_sep/

README

Privilege separation library for Unix-likes OSes

git crates.io docs.rs

priv_sep is a library that uses the system's libc to perform privilege separation and privilege reduction for Unix-like platforms. The following target_os values are supported:

  • dragonfly
  • freebsd
  • linux
  • macos
  • netbsd
  • openbsd

priv_sep in action

use core::convert::Infallible;
use priv_sep::{PrivDropErr, UserInfo};
use std::{
    io::Error,
    net::{Ipv6Addr, SocketAddrV6},
};
use tokio::net::TcpListener;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<Infallible, PrivDropErr<Error>> {
    // Get the user ID and group ID for nobody from `passwd(5)`.
    // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
    // Bind to TCP `[::1]:443` as root.
    // `setgroups(2)` to drop all supplementary groups.
    // `setresgid(2)` to the group ID associated with nobody.
    // `setresuid(2)` to the user ID associated with nobody.
    let listener =
        UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || {
            TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
        })
        .await?;
    // At this point, the process is running under nobody.
    loop {
        // Handle TCP connections.
        if let Ok((_, ip)) = listener.accept().await {
            assert!(ip.is_ipv6());
        }
    }
}
Incorporating pledge(2) and unveil(2) on OpenBSD
use core::convert::Infallible;
use priv_sep::{Permissions, PrivDropErr, Promise, Promises};
use std::{
    fs,
    io::Error,
    net::{Ipv6Addr, SocketAddrV6},
};
use tokio::net::TcpListener;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<Infallible, PrivDropErr<Error>> {
    /// Config file.
    const CONFIG: &str = "config";
    // Get the user ID and group ID for nobody from `passwd(5)`.
    // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
    // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
    // Bind to TCP `[::1]:443` as root.
    // `setgroups(2)` to drop all supplementary groups.
    // `setresgid(2)` to the group ID associated with nobody.
    // `setresuid(2)` to the user ID associated with nobody.
    // Remove `id` from our `pledge(2)`d promises.
    let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async(
        c"nobody",
        c"/path/chroot/",
        [Promise::Inet, Promise::Rpath, Promise::Unveil],
        false,
        async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await,
    )
    .await?;
    // At this point, the process is running under nobody.
    // Only allow file system access to `config` and only allow read access to it.
    Permissions::READ.unveil(CONFIG)?;
    // Read `config`.
    // This will of course fail if the file does not exist or nobody does not
    // have read permissions.
    let config = fs::read(CONFIG).map_err(PrivDropErr::Other)?;
    // Remove file system access.
    Permissions::NONE.unveil(CONFIG)?;
    // Remove `rpath` and `unveil` from our `pledge(2)`d promises
    // (i.e., only have `inet` and `stdio` abilities when we begin accepting TCP connections).
    promises.remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])?;
    loop {
        // Handle TCP connections.
        if let Ok((_, ip)) = listener.accept().await {
            assert!(ip.is_ipv6());
        }
    }
}

Cargo "features"

alloc

Enables alloc support. While "typical" use of priv_sep should work without alloc, there are cases where one may desire heap allocation. For example if a database entry associated with a user requires more than 1 KiB of space, UserInfo::new will error with Errno::ERANGE when alloc is not enabled.

Additional CStrHelper impls are exposed as well (e.g., String).

std

Enables std support. This is useful for additional CStrHelper impls (e.g., OsStr) as well as TryFrom<Error> and From<Errno>.

This feature implies alloc and is enabled by default via the default feature.

Minimum Supported Rust Version (MSRV)

This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV will be updated.

MSRV changes will correspond to a SemVer minor version bump.

SemVer Policy

  • All on-by-default features of this library are covered by SemVer
  • MSRV is considered exempt from SemVer as noted above

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Before any PR is sent, cargo clippy --all-targets, cargo test --all-targets, and cargo test --doc should be run for each possible combination of "features" using the stable and MSRV toolchains. One easy way to achieve this is by invoking ci-cargo as ci-cargo clippy --all-targets test --all-targets in the priv_sep directory.

Additionally, one should test all ignore tests as both root and non-root for both toolchains. These tests should be run individually since they may interfere with each other.

Last, RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features -Zbuild-std=std should be run to ensure documentation can be built.

Status

The crate is only tested on the x86_64-unknown-linux-gnu, x86_64-unknown-openbsd, and aarch64-apple-darwin targets; but it should work on most of the supported platforms.

Commit count: 0

cargo fmt