capctl
A pure-Rust interface to prctl() and Linux capabilities
Repository Health
Technical Analysis
capctl is a pure-Rust library for querying and modifying Linux capabilities and calling prctl() directly, without hand-rolling libc bindings or raw bitmask math. It exposes typed wrappers around the five kernel capability sets (permitted, effective, inheritable, bounding, and ambient) instead of collapsing them into one unified interface, so the real differences between them stay visible in the API rather than being papered over.
The crate supports no_std environments via its std feature flag, offers optional inline syscalls through the sc crate to skip libc for a handful of hot-path operations, and provides serde support for serializing capability state. It positions itself as a lower-level, more precise alternative to the caps crate for programs that need fine-grained control over privilege dropping and capability inspection.
What You Get
- A typed Cap enum covering all 41 Linux capabilities with CAP_-prefixed name parsing and Display
- A CapSet bitset type for efficient set operations (union, intersection, iteration) instead of HashSet<Cap>
- Separate ambient and bounding set modules that mirror the kernel’s distinct per-set APIs rather than unifying them
- CapState and FullCapState for reading and atomically setting the permitted/effective/inheritable sets
- prctl() wrappers for no-new-privs, thread naming, securebits, and other non-capability prctl operations
- Optional serde Serialize/Deserialize impls and no_std support via feature flags
Common Use Cases
- Dropping unneeded capabilities before exec’ing an untrusted or lower-trust child process
- Building sandboxing or init-style tools (container runtimes, service supervisors) that manage privilege sets explicitly
- Auditing or logging a process’s effective capabilities for security tooling
- Setting no-new-privs and other prctl() flags as part of a privilege-dropping sequence
Under The Hood
Architecture capctl separates concerns into two top-level modules — caps (Linux capability sets) and prctl (all other prctl() operations) — reexported flatly from lib.rs. Under caps, each kernel-level capability set gets its own dedicated interface: ambient.rs and bounding.rs expose free functions matching the kernel’s actual per-set semantics (rather than unifying them behind one trait), while capstate.rs (CapState) and fullcapstate.rs (FullCapState) wrap the combined permitted/effective/inheritable triad read via capget()/capset(). All raw syscalls funnel through two central primitives in lib.rs, raw_prctl and raw_prctl_opt, the sole unsafe boundary, calling either libc::prctl() or, when the sc feature is enabled, an inline syscall via the sc crate selected at compile time with cfg_if!. CapSet (capset.rs, 738 lines) is a bitmask type implementing the full complement of Rust set traits (BitAnd/BitOr/Sub/Not, FromIterator) so it behaves like a real collection while storing capabilities as a single u64. Errors from every raw call surface through one Error(i32) newtype (err.rs) wrapping errno, with Display resolving the message via libc::strerror_r and an explicit From<Error> for std::io::Error conversion gated on the std feature.
Tech Stack Pure Rust, edition 2018, rust-version 1.63 minimum. Runtime dependencies are libc 0.2 (default-features=false, so it works in no_std builds), cfg-if 1.0 for the sc/non-sc syscall dispatch, and bitflags 1.3; serde 1.0 and the sc syscall crate are both optional, feature-gated dependencies rather than defaults. The crate’s own std feature (enabled by default) gates every function touching heap-allocated types like String/Vec/OsString, so disabling it produces a genuinely #![no_std] build via #![cfg_attr(not(feature = “std”), no_std)] in lib.rs. Dev-dependencies are limited to serde_test for round-tripping the optional Serialize/Deserialize impls. There is no build.rs and no unsafe FFI beyond libc/sc calls.
Code Quality Testing is dense and inline (#[cfg(test)] mod tests in nearly every source file — capset.rs alone has 19 #[test] functions covering bit-level invariants like has()/add()/drop() and the trait impls) rather than living in a separate tests/ directory; err.rs and caps/mod.rs each carry 6-11 tests exercising errno formatting and Cap enum edge cases, including verifying CAP_MAX bit-math against every defined capability. Unsafe code is confined almost entirely to the two raw_prctl* functions in lib.rs and is commented with an explicit warning that unsafe code trusts the LAST_CAP constant to be correct, in caps/mod.rs. Error handling is consistent throughout: fallible operations return a shared crate::Result<T> rather than panicking, and the one library-defined FromStr impl (Cap) returns a dedicated ParseCapError instead of the generic Error type. Naming is uniformly snake_case and kernel-mirroring (bounding::drop(), ambient::raise()), and public items carry doc comments referencing the underlying man pages.
API Design The crate optimizes for correctness over convenience by design — its README explicitly argues against the unified-capability-set abstraction used by the competing caps crate, on the grounds that collapsing the five kernel sets into one interface hides operations that are actually impossible on some sets and can cause extra syscalls. That tradeoff means callers need to understand which of ambient/bounding/CapState applies to their use case rather than calling one generic method, a real learning-curve cost in a security-sensitive domain that isn’t heavily documented outside module-level doc comments. Where it succeeds ergonomically: Cap implements FromStr/Display for CAP_-prefixed names, CapSet implements the standard Rust bit-set operator traits so set math reads naturally (a | b, a - b), and errors convert cleanly into std::io::Error for callers who want to propagate with ?. There are no example files under an examples/ directory, so newcomers are pointed at docs.rs and the module doc comments rather than runnable code.