27 releases (5 stable)
Uses new Rust 2024
| 3.0.0-alpha.5.0 | Mar 10, 2026 |
|---|---|
| 3.0.0-alpha.4.0 | Jan 11, 2026 |
| 3.0.0-alpha.3.0 | Nov 13, 2025 |
| 3.0.0-alpha.1.2 | Jul 30, 2025 |
| 0.4.0 | Jul 26, 2023 |
#138 in Unix APIs
Used in 4 crates
235KB
4.5K
SLoC
Privilege separation library for Unix-likes OSes
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:
dragonflyfreebsdlinuxmacosnetbsdopenbsd
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
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
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.