capctl-0.2.4/.cargo_vcs_info.json0000644000000001360000000000100123050ustar { "git": { "sha1": "6b89ddb3e79493a5e34bb681c00053d6122968bd" }, "path_in_vcs": "" }capctl-0.2.4/.github/workflows/ci.yml000064400000000000000000000061601046102023000156130ustar 00000000000000name: CI on: [push, pull_request] defaults: run: shell: bash jobs: build: name: Build strategy: fail-fast: false matrix: toolchain: [stable, beta, nightly] target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl - i686-unknown-linux-gnu - i686-unknown-linux-musl features: - '' - serde - std serde include: - toolchain: nightly target: x86_64-unknown-linux-gnu features: sc # Allow nightly builds to fail continue-on-error: ${{ matrix.toolchain == 'nightly' }} runs-on: ubuntu-latest steps: - name: Set up repo uses: actions/checkout@v2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} - name: Install 32-bit glibc build dependencies run: sudo apt-get update && sudo apt-get -y install gcc-multilib if: matrix.target == 'i686-unknown-linux-gnu' - name: Build run: cargo +${{ matrix.toolchain }} build --verbose --target ${{ matrix.target }} --no-default-features --features "${{ matrix.features }}" - name: Run tests run: cargo +${{ matrix.toolchain }} test --verbose --target ${{ matrix.target }} --no-default-features --features "${{ matrix.features }}" - name: Run tests (in user namespace) run: unshare -r cargo +${{ matrix.toolchain }} test --verbose --target ${{ matrix.target }} --no-default-features --features "${{ matrix.features }}" coverage-tarpaulin: name: Tarpaulin strategy: fail-fast: false matrix: toolchain: [stable] target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl features: - '' prefix: - '' - unshare -r include: - toolchain: nightly target: x86_64-unknown-linux-gnu features: sc prefix: '' - toolchain: nightly target: x86_64-unknown-linux-gnu features: sc prefix: unshare -r runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} - name: Install tarpaulin run: cargo install cargo-tarpaulin - name: Run tarpaulin run: ${{ matrix.prefix }} cargo +${{ matrix.toolchain }} tarpaulin --verbose --out Xml --target ${{ matrix.target }} --features "${{ matrix.features }}" - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: name: ${{ matrix.toolchain }}-${{ matrix.target }} fail_ci_if_error: true env_vars: OS,TARGET,TOOLCHAIN,JOB env: JOB: ${{ github.job }} OS: ${{ matrix.os }} TARGET: ${{ matrix.target }} TOOLCHAIN: ${{ matrix.toolchain }} FEATURES: ${{ matrix.features }} capctl-0.2.4/.gitignore000064400000000000000000000000531046102023000130630ustar 00000000000000/target /tarpaulin-report.html /Cargo.lock capctl-0.2.4/Cargo.toml0000644000000024240000000000100103050ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "capctl" version = "0.2.4" authors = ["cptpcrd"] description = "A pure-Rust interface to prctl() and Linux capabilities." readme = "README.md" keywords = [ "prctl", "capabilities", ] categories = ["os::linux-apis"] license = "MIT" repository = "https://github.com/cptpcrd/capctl" [package.metadata.docs.rs] all-features = true rustc-args = [ "--cfg", "docsrs", ] targets = ["x86_64-unknown-linux-gnu"] [dependencies.bitflags] version = "1.3" [dependencies.cfg-if] version = "1.0" [dependencies.libc] version = "0.2" default-features = false [dependencies.sc] version = "0.2" optional = true [dependencies.serde] version = "1.0" features = ["derive"] optional = true default-features = false [dev-dependencies.serde_test] version = "1.0" [features] default = ["std"] std = [] capctl-0.2.4/Cargo.toml.orig000064400000000000000000000013561046102023000137710ustar 00000000000000[package] name = "capctl" version = "0.2.4" edition = "2018" description = "A pure-Rust interface to prctl() and Linux capabilities." readme = "README.md" authors = ["cptpcrd"] license = "MIT" categories = ["os::linux-apis"] keywords = ["prctl", "capabilities"] repository = "https://github.com/cptpcrd/capctl" [features] default = ["std"] std = [] [dependencies] libc = { version = "0.2", default-features = false } cfg-if = "1.0" bitflags = "1.3" serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } sc = { version = "0.2", optional = true } [dev-dependencies] serde_test = "1.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] all-features = true rustc-args = ["--cfg", "docsrs"] capctl-0.2.4/LICENSE000064400000000000000000000020501046102023000120770ustar 00000000000000MIT License Copyright (c) 2020 cptpcrd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. capctl-0.2.4/README.md000064400000000000000000000072141046102023000123600ustar 00000000000000# capctl [![crates.io](https://img.shields.io/crates/v/capctl.svg)](https://crates.io/crates/capctl) [![Docs](https://docs.rs/capctl/badge.svg)](https://docs.rs/capctl) [![GitHub Actions](https://github.com/cptpcrd/capctl/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/cptpcrd/capctl/actions?query=workflow%3ACI+branch%3Amaster+event%3Apush) [![codecov](https://codecov.io/gh/cptpcrd/capctl/branch/master/graph/badge.svg)](https://codecov.io/gh/cptpcrd/capctl) A pure-Rust interface to `prctl()` and Linux capabilities. ### Features This crate has the following features (by default, only `std` is enabled): - `std`: Link against the standard library. Interfaces that depend on this feature are marked in the [documentation on docs.rs](https://docs.rs/capctl). - `sc`: Allow making inline syscalls with the `sc` crate instead of calling into the system's libc for *some* operations. *Note: Currently, support for inline syscalls is limited to the following syscalls: `prctl()`, `capget()`, `capset()`, `setresuid()`, `setresgid()`, `setgroups()`. `capctl` will still call into the system's libc for most other syscalls.* - `serde`: Enables implementations of `Serialize` and `Deserialize` for most (non-error) types. ### Why not [`caps`](https://crates.io/crates/caps)? **TL;DR**: In the opinion of `capctl`'s author, `caps` adds too much abstraction and overhead. 1. The kernel APIs to access the 5 capability sets (permitted, effective, inheritable, bounding, and ambient) are very different. However, `caps` presents a unified interface that allows for manipulating all of them the same way. This is certainly more convenient to use. However, a) it minimizes the differences between the capabilities sets (something that is fundamental and must be understood to use capabilities properly), b) it allows users to write code that attempts to perform operations that are actually impossible (i.e. adding capabilities to the bounding capability set), and c) it can result in excessive syscalls (because operations that the kernel APIs allow to be performed together instead must done separately). Note: The author of `capctl` is not *completely* opposed to adding these kinds of interfaces, provided that lower-level APIs are also provided to allow users finer control. `caps`, however, does not do this. 2. `capctl` uses more efficient representations internally. For example, `caps` uses `HashSet`s to store sets of capabilities, which is wasteful. `capctl`, meanwhile, has a custom `CapSet` struct that stores a set of capabilities much more efficiently. (`CapSet` also has methods specially designed to work with capabilities, instead of just being a generalized set implementation.) ### Why not [`prctl`](https://crates.io/crates/prctl)? **TL;DR**: `prctl` is a very low-level wrapper crate, and some of its "safe" code *should* be `unsafe`. 1. `prctl` concentrates on the `prctl()` system call, not Linux capabilities in general. As a result, its interface to Linux capabilities is an afterthought and incomplete. 2. `prctl` returns raw `errno` values when an error occurs. This crate returns a friendlier custom error type that can be converted into an `io::Error`. 3. Most importantly, `prctl` fails to recognize that, as the man page explains, `prctl()` is a very low-level syscall, and it should be used cautiously. As a result, some of the "safe" functions in `prctl` are actually highly unsafe! `prctl::set_mm()` is the worst example: it can be used to set raw addresses, such as the end of the heap (as with `brk()`), and it's a "safe" function! It even accepts these addresses as `libc::c_ulong`s instead of raw pointers, making it easy to abuse. capctl-0.2.4/codecov.yml000064400000000000000000000001061046102023000132370ustar 00000000000000coverage: status: project: on patch: off ignore: - tests capctl-0.2.4/src/caps/ambient.rs000064400000000000000000000151371046102023000146060ustar 00000000000000use super::{Cap, CapSet}; /// Raise the given capability in the current thread's ambient capability set. #[inline] pub fn raise(cap: Cap) -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_CAP_AMBIENT, libc::PR_CAP_AMBIENT_RAISE as libc::c_ulong, cap as libc::c_ulong, 0, 0, )?; } Ok(()) } /// Lower the given capability in the current thread's ambient capability set. #[inline] pub fn lower(cap: Cap) -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_CAP_AMBIENT, libc::PR_CAP_AMBIENT_LOWER as libc::c_ulong, cap as libc::c_ulong, 0, 0, )?; } Ok(()) } /// Check whether the given capability is raised in the current thread's ambient capability set. /// /// This returns `Some(true)` if the given capability is raised, `Some(false)` if it is lowered, /// and `None` if it is not supported. #[inline] pub fn is_set(cap: Cap) -> Option { match unsafe { crate::raw_prctl_opt( libc::PR_CAP_AMBIENT, libc::PR_CAP_AMBIENT_IS_SET as libc::c_ulong, cap as libc::c_ulong, 0, 0, ) } { Some(res) => Some(res != 0), None => { #[cfg(not(feature = "sc"))] debug_assert_eq!(unsafe { *libc::__errno_location() }, libc::EINVAL); None } } } /// Clear the current thread's ambient capability set. /// /// This is a single `prctl()` call (`PR_CAP_AMBIENT_CLEAR_ALL`) that removes all capabilities /// supported by the kernel from the ambient set. #[inline] pub fn clear() -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_CAP_AMBIENT, libc::PR_CAP_AMBIENT_CLEAR_ALL as libc::c_ulong, 0, 0, 0, )?; } Ok(()) } /// Check whether ambient capabilities are supported on the running kernel. #[inline] pub fn is_supported() -> bool { is_set(Cap::CHOWN).is_some() } /// "Probes" the current thread's ambient capability set and returns a `CapSet` representing all /// the capabilities that are currently raised. /// /// Returns `None` if ambient capabilities are not supported on the running kernel. pub fn probe() -> Option { let mut set = CapSet::empty(); for cap in Cap::iter() { match is_set(cap) { Some(true) => set.add(cap), Some(false) => (), // Unsupported capability encountered; none of the remaining ones will be supported // either None => { if cap as u8 == 0 { // Ambient capabilities aren't supported at all return None; } else { break; } } } } Some(set) } /// Drop all bounding capabilities that are supported by the kernel but which this library is not /// aware of from the current thread's ambient capability set. /// /// See [Handling of newly-added capabilities](../index.html#handling-of-newly-added-capabilities) /// for the rationale. pub fn clear_unknown() -> crate::Result<()> { for cap in (super::CAP_MAX as libc::c_ulong + 1)..(super::CAP_MAX as libc::c_ulong * 2) { match unsafe { crate::raw_prctl( libc::PR_CAP_AMBIENT, libc::PR_CAP_AMBIENT_LOWER as libc::c_ulong, cap, 0, 0, ) } { Ok(_) => (), Err(e) if e.code() == libc::EINVAL => return Ok(()), Err(e) => return Err(e), } } Err(crate::Error::from_code(libc::E2BIG)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_ambient_supported() { if is_supported() { let orig_caps = probe().unwrap(); let supported_caps = Cap::probe_supported(); // Make sure everything is consistent for cap in Cap::iter() { if supported_caps.has(cap) { assert_eq!(is_set(cap), Some(orig_caps.has(cap)), "{:?}", cap); } else { assert_eq!(is_set(cap), None, "{:?}", cap); } } // Clear unknown capabilities from the ambient capability set clear_unknown().unwrap(); assert_eq!(probe().unwrap(), orig_caps); // Clear the ambient capability set clear().unwrap(); // Now make sure it's actually empty assert_eq!(probe().unwrap(), CapSet::empty()); // Now test actually raising capabilities let mut orig_state = crate::caps::CapState::get_current().unwrap(); let mut state = orig_state; // To start, copy the permitted set to the inheritable set state.inheritable = state.permitted; state.set_current().unwrap(); // Now raise all of those capabilities in the ambient set for cap in state.inheritable { raise(cap).unwrap(); } // Now clear the inheritable set state.inheritable.clear(); state.set_current().unwrap(); // The ambient set should be automatically cleared assert_eq!(probe().unwrap(), CapSet::empty()); // And trying to raise any capability in the ambient set will now fail for cap in supported_caps { assert_eq!(raise(cap).unwrap_err().code(), libc::EPERM); } // Restore the original capability state at the end (to the extent we're able) orig_state.inheritable &= orig_state.permitted; orig_state.set_current().unwrap(); // Raise all the capabilities that were in there originally for cap in orig_caps.iter() { raise(cap).unwrap(); } // Lower all the ones that weren't for cap in (supported_caps - orig_caps).iter() { lower(cap).unwrap(); } for cap in !supported_caps { assert_eq!(raise(cap).unwrap_err().code(), libc::EINVAL); assert_eq!(lower(cap).unwrap_err().code(), libc::EINVAL); } } else { assert_eq!(probe(), None); assert_eq!(raise(Cap::CHOWN).unwrap_err().code(), libc::EINVAL); assert_eq!(lower(Cap::CHOWN).unwrap_err().code(), libc::EINVAL); assert_eq!(clear().unwrap_err().code(), libc::EINVAL); assert_eq!(clear_unknown().unwrap_err().code(), libc::EINVAL); } } } capctl-0.2.4/src/caps/bounding.rs000064400000000000000000000171561046102023000147770ustar 00000000000000use super::{Cap, CapSet}; /// Drop the given capability from the current thread's bounding capability set. /// /// Note that this will fail with `EPERM` if the current thread does not have `CAP_SETPCAP`, even /// if the given capability is already lowered. Callers may wish to use [`ensure_dropped()`] /// instead. #[inline] pub fn drop(cap: Cap) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_CAPBSET_DROP, cap as libc::c_ulong, 0, 0, 0) }?; Ok(()) } /// Check if the given capability is raised in the current thread's bounding capability set. /// /// This returns `Some(true)` if the given capability is raised, `Some(false)` if it is lowered, and /// `None` if it is not supported. #[inline] pub fn read(cap: Cap) -> Option { read_raw(cap as _) } #[inline] fn read_raw(cap: libc::c_ulong) -> Option { match unsafe { crate::raw_prctl_opt(libc::PR_CAPBSET_READ, cap, 0, 0, 0) } { Some(res) => Some(res != 0), None => { #[cfg(not(feature = "sc"))] debug_assert_eq!(unsafe { *libc::__errno_location() }, libc::EINVAL); None } } } /// Check if the given capability is raised in the current thread's bounding capability set. /// /// This is an alias of [`read()`](./fn.read.html). #[deprecated(since = "0.2.1", note = "use `read()` instead")] #[inline] pub fn is_set(cap: Cap) -> Option { read(cap) } /// "Probes" the current thread's bounding capability set and returns a `CapSet` representing all /// the capabilities that are currently raised. pub fn probe() -> CapSet { let mut set = CapSet::empty(); for cap in Cap::iter() { match read(cap) { Some(true) => set.add(cap), Some(false) => (), // Unsupported capability encountered; none of the remaining ones will be supported // either _ => break, } } set } /// Try to ensure that the given capability is dropped. /// /// This is a helper that first tries to drop the capability. If that fails with `EPERM`, it /// `read()`s the capability to see if it's already lowered. /// /// This function will: /// /// - Return `Ok(())` if the capability is now lowered. /// - Fail with `EPERM` if the capability is raised and the current thread does not have /// `CAP_SETPCAP`. /// - Fail with `EINVAL` if the capability is not supported by the kernel. pub fn ensure_dropped(cap: Cap) -> crate::Result<()> { ensure_dropped_raw(cap as _) } #[inline] fn ensure_dropped_raw(cap: libc::c_ulong) -> crate::Result<()> { match unsafe { crate::raw_prctl(libc::PR_CAPBSET_DROP, cap, 0, 0, 0) } { // Successfully lowered the capability Ok(_) => Ok(()), // EPERM -> we don't have CAP_SETPCAP // Check the current setting of the capability to decide what to do Err(e) if e.code() == libc::EPERM => match read_raw(cap) { // The capability is raised -> pass up EPERM to the caller Some(true) => Err(e), // The capability is lowered -> we have nothing to do Some(false) => Ok(()), // The capability is unsupported None => Err(crate::Error::from_code(libc::EINVAL)), }, // Pass all other errors up to the caller Err(e) => Err(e), } } fn clear_from(low: libc::c_ulong) -> crate::Result<()> { for cap in low..(super::CAP_MAX as libc::c_ulong * 2) { match ensure_dropped_raw(cap) { Ok(()) => (), // Unknown capability // If cap is not 0, we found the last capability Err(e) if e.code() == libc::EINVAL && cap != 0 => return Ok(()), // Pass all other errors up to the caller Err(e) => return Err(e), } } Err(crate::Error::from_code(libc::E2BIG)) } /// Drop all capabilities supported by the kernel from the current thread's bounding capability set. /// /// This method is roughly equivalent to the following (though it may be slightly faster): /// /// ```no_run /// # use capctl::*; /// # fn clear() -> Result<()> { /// for cap in Cap::iter() { /// if bounding::read(cap) == Some(true) { /// bounding::drop(cap)?; /// } /// } /// bounding::clear_unknown()?; /// # Ok(()) /// # } /// ``` /// /// The intent is to simulate [`crate::caps::ambient::clear()`] (which is a single `prctl()` call). /// /// See also [`clear_unknown()`]. #[inline] pub fn clear() -> crate::Result<()> { clear_from(0) } /// Drop all capabilities that are supported by the kernel but which this library is not aware of /// from the current thread's bounding capability set. /// /// For example, this code will drop all bounding capabilities (even ones not supported by /// `capctl`) except for `CAP_SETUID`: /// /// ```no_run /// # use capctl::*; /// // Drop all capabilities that `capctl` knows about (except for CAP_SETUID) /// for cap in Cap::iter() { /// if cap != Cap::SETUID { /// bounding::drop(cap).unwrap(); /// } /// } /// // Drop any new capabilities that `capctl` wasn't aware of at compile time /// bounding::clear_unknown(); /// ``` /// /// See [Handling of newly-added capabilities](../index.html#handling-of-newly-added-capabilities) /// for the rationale. #[inline] pub fn clear_unknown() -> crate::Result<()> { clear_from((super::CAP_MAX + 1) as _) } #[cfg(test)] mod tests { use super::*; #[test] fn test_bounding() { probe(); read(Cap::CHOWN).unwrap(); } #[test] fn test_bounding_drop() { if crate::caps::CapState::get_current() .unwrap() .effective .has(crate::caps::Cap::SETPCAP) { assert!(read(crate::caps::Cap::SETPCAP).unwrap()); drop(crate::caps::Cap::SETPCAP).unwrap(); assert!(!read(crate::caps::Cap::SETPCAP).unwrap()); } else { assert_eq!( drop(crate::caps::Cap::SETPCAP).unwrap_err().code(), libc::EPERM ); } } #[test] fn test_clear() { let mut state = crate::caps::CapState::get_current().unwrap(); if state.effective.has(crate::caps::Cap::SETPCAP) || probe().is_empty() { clear().unwrap(); assert_eq!(probe(), crate::caps::CapSet::empty()); clear().unwrap(); assert_eq!(probe(), crate::caps::CapSet::empty()); state.effective.drop(crate::caps::Cap::SETPCAP); state.set_current().unwrap(); clear().unwrap(); assert_eq!(probe(), crate::caps::CapSet::empty()); } else { assert_eq!(clear().unwrap_err().code(), libc::EPERM); } } #[test] fn test_clear_unknown() { let mut state = crate::caps::CapState::get_current().unwrap(); if state.effective.has(crate::caps::Cap::SETPCAP) || read_raw((super::super::CAP_MAX + 1) as _).is_none() { // Either we have CAP_SETPCAP, or there are no unknown capabilities let orig_caps = probe(); clear_unknown().unwrap(); assert_eq!(probe(), orig_caps); clear_unknown().unwrap(); assert_eq!(probe(), orig_caps); // If there are unknown capabilities, the first one is NOT raised in the bounding set assert!(matches!( read_raw((super::super::CAP_MAX + 1) as _), Some(false) | None )); state.effective.drop(crate::caps::Cap::SETPCAP); state.set_current().unwrap(); clear_unknown().unwrap(); assert_eq!(probe(), orig_caps); } else { assert_eq!(clear_unknown().unwrap_err().code(), libc::EPERM); } } } capctl-0.2.4/src/caps/cap_text.rs000064400000000000000000000327041046102023000147750ustar 00000000000000use core::fmt; use crate::caps::{CapSet, CapState, NUM_CAPS}; pub fn caps_from_text(s: &str) -> Result { let s = s.trim(); if s.is_empty() { return Err(ParseCapsError::InvalidFormat); } let mut res = CapState::empty(); for part in s.split_whitespace() { update_capstate_single(part, &mut res)?; } Ok(res) } fn update_capstate_single(s: &str, state: &mut CapState) -> Result<(), ParseCapsError> { let index = match s.find(|c| c == '+' || c == '-' || c == '=') { Some(i) => i, None => return Err(ParseCapsError::InvalidFormat), }; if index == 0 && !s.starts_with('=') { // Example: "+eip" or "-eip" return Err(ParseCapsError::InvalidFormat); } let spec_caps = parse_capset(&s[..index])?; let mut should_raise = true; let mut last_ch = None; for ch in s[index..].chars() { match ch { '=' | '+' | '-' => match last_ch { // No "+/-/=" following each other Some('=') | Some('+') | Some('-') => return Err(ParseCapsError::InvalidFormat), _ => (), }, 'p' | 'i' | 'e' => debug_assert!(last_ch.is_some()), _ => return Err(ParseCapsError::InvalidFormat), } let set = match ch { '=' => { state.effective -= spec_caps; state.inheritable -= spec_caps; state.permitted -= spec_caps; should_raise = true; None } '+' => { should_raise = true; None } '-' => { should_raise = false; None } 'p' => Some(&mut state.permitted), 'i' => Some(&mut state.inheritable), 'e' => Some(&mut state.effective), _ => unreachable!(), }; if let Some(set) = set { if should_raise { *set |= spec_caps; } else { *set -= spec_caps; } } last_ch = Some(ch); } Ok(()) } fn parse_capset(s: &str) -> Result { if s.is_empty() || s.eq_ignore_ascii_case("all") { return Ok(!CapSet::empty()); } let mut res = CapSet::empty(); for part in s.split(',') { match part.parse() { Ok(cap) => res.add(cap), Err(_) => { return Err(ParseCapsError::UnknownCapability); } } } Ok(res) } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum ParseCapsError { InvalidFormat, UnknownCapability, BadFileEffective, } impl ParseCapsError { fn desc(&self) -> &str { match *self { Self::InvalidFormat => "Invalid format", Self::UnknownCapability => "Unknown capability", Self::BadFileEffective => "Effective set must be either empty or same as permitted set", } } } impl fmt::Display for ParseCapsError { #[allow(deprecated)] #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.desc()) } } #[cfg(feature = "std")] impl std::error::Error for ParseCapsError {} pub fn caps_to_text(mut state: CapState, f: &mut fmt::Formatter) -> fmt::Result { if state == CapState::empty() { return f.write_char('='); } else if state == (CapState { effective: !CapSet::empty(), permitted: !CapSet::empty(), inheritable: !CapSet::empty(), }) { return f.write_str("=eip"); } let orig_state = state; use core::fmt::Write; fn format_capset(f: &mut fmt::Formatter, caps: &CapSet, prefix_ch: char) -> fmt::Result { debug_assert!(!caps.is_empty()); if *caps == !CapSet::empty() { // Full set if prefix_ch != '=' { f.write_str("all")?; } } else { // Note: This must be big enough to hold the name of any capability let mut buf = [0u8; 30]; for (i, cap) in caps.iter().enumerate() { f.write_str(if i == 0 { "cap_" } else { ",cap_" })?; // Copy the capability's name into the buffer let orig_name = cap.name().as_bytes(); let name = &mut buf[..orig_name.len()]; name.copy_from_slice(orig_name); // Safety: We just copied this byte-for-byte from a valid UTF-8 string let name = unsafe { core::str::from_utf8_unchecked_mut(name) }; // Convert it to lowercase and write it name.make_ascii_lowercase(); f.write_str(name)?; } } Ok(()) } let mut first = true; fn format_part( f: &mut fmt::Formatter, orig_state: &CapState, state: &mut CapState, drop_state: &mut CapState, first: &mut bool, effective: bool, inheritable: bool, permitted: bool, ) -> fmt::Result { let mut caps = !CapSet::empty(); debug_assert!(effective || inheritable || permitted); if effective { caps &= state.effective; } if inheritable { caps &= state.inheritable; } if permitted { caps &= state.permitted; } if caps.is_empty() { return Ok(()); } if NUM_CAPS as usize - caps.size() <= 10 { caps = !CapSet::empty(); } let prefix_ch = if *first { '=' } else { '+' }; if *first { *first = false; } else { f.write_char(' ')?; } format_capset(f, &caps, prefix_ch)?; f.write_char(prefix_ch)?; if effective { f.write_char('e')?; drop_state.effective |= caps - orig_state.effective; state.effective -= caps; } if inheritable { f.write_char('i')?; drop_state.inheritable |= caps - orig_state.inheritable; state.inheritable -= caps; } if permitted { f.write_char('p')?; drop_state.permitted |= caps - orig_state.permitted; state.permitted -= caps; } Ok(()) } fn format_part_drop( f: &mut fmt::Formatter, drop_state: &mut CapState, effective: bool, inheritable: bool, permitted: bool, ) -> fmt::Result { let mut drop_caps = !CapSet::empty(); debug_assert!(effective || inheritable || permitted); if effective { drop_caps &= drop_state.effective; } if inheritable { drop_caps &= drop_state.inheritable; } if permitted { drop_caps &= drop_state.permitted; } if drop_caps.is_empty() { return Ok(()); } f.write_char(' ')?; format_capset(f, &drop_caps, '-')?; f.write_char('-')?; if effective { f.write_char('e')?; drop_state.effective -= drop_caps; } if inheritable { f.write_char('i')?; drop_state.inheritable -= drop_caps; } if permitted { f.write_char('p')?; drop_state.permitted -= drop_caps; } Ok(()) } // This stores the capabilities that need to be *dropped* from each set. // For example, if the effective and permitted sets are full except for CAP_CHOWN, we generate // `all=ep cap_chown-ep`. Immediately after we generate `all=ep`, drop_state.effective and // drop_state.permitted will both be holding just CAP_CHOWN. Later, we revisit that, see that // CAP_CHOWN is set, and generate `cap_chown-ep`. let mut drop_state = CapState::empty(); format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, true, true, true, )?; format_part_drop(f, &mut drop_state, true, true, true)?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, true, true, false, )?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, false, true, true, )?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, true, false, true, )?; format_part_drop(f, &mut drop_state, true, true, false)?; format_part_drop(f, &mut drop_state, false, true, true)?; format_part_drop(f, &mut drop_state, true, false, true)?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, true, false, false, )?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, false, true, false, )?; format_part( f, &orig_state, &mut state, &mut drop_state, &mut first, false, false, true, )?; format_part_drop(f, &mut drop_state, true, false, false)?; format_part_drop(f, &mut drop_state, false, true, false)?; format_part_drop(f, &mut drop_state, false, false, true)?; debug_assert_eq!(state, CapState::empty()); debug_assert_eq!(drop_state, CapState::empty()); Ok(()) } #[cfg(all(test, feature = "std"))] mod tests { use super::*; use crate::caps::Cap; use crate::capset; #[test] fn test_parse_capset() { assert_eq!(parse_capset("").unwrap(), !CapSet::empty()); assert_eq!(parse_capset("all").unwrap(), !CapSet::empty()); assert_eq!(parse_capset("ALL").unwrap(), !CapSet::empty()); assert_eq!(parse_capset("cap_chown").unwrap(), capset!(Cap::CHOWN)); assert_eq!(parse_capset("CAP_CHOWN").unwrap(), capset!(Cap::CHOWN)); assert_eq!( parse_capset("cap_chown,cap_syslog").unwrap(), capset!(Cap::CHOWN, Cap::SYSLOG), ); assert_eq!( parse_capset("cap_noexist").unwrap_err().to_string(), "Unknown capability" ); assert_eq!( parse_capset(",").unwrap_err().to_string(), "Unknown capability" ); } #[test] fn test_parse_capstate() { assert_eq!( caps_from_text("").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text(" ").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("cap_chown").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("+eip").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("-eip").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("cap_chown+-p").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("cap_chown=-p").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("cap_chown+y").unwrap_err().to_string(), "Invalid format" ); assert_eq!( caps_from_text("cap_noexist+p").unwrap_err().to_string(), "Unknown capability" ); assert_eq!( caps_from_text("cap_chown=p").unwrap(), CapState { permitted: capset!(Cap::CHOWN), effective: capset!(), inheritable: capset!(), } ); assert_eq!( caps_from_text("cap_chown+p").unwrap(), CapState { permitted: capset!(Cap::CHOWN), effective: capset!(), inheritable: capset!(), } ); assert_eq!( caps_from_text("cap_chown+ie").unwrap(), CapState { permitted: capset!(), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), } ); assert_eq!( caps_from_text("=e cap_chown-e").unwrap(), CapState { permitted: capset!(), effective: !capset!(Cap::CHOWN), inheritable: capset!(), } ); assert_eq!( caps_from_text("=e").unwrap(), CapState { permitted: capset!(), effective: !capset!(), inheritable: capset!(), } ); assert_eq!( caps_from_text("all=e").unwrap(), CapState { permitted: capset!(), effective: !capset!(), inheritable: capset!(), } ); assert_eq!( caps_from_text("=eip cap_chown-e").unwrap(), CapState { permitted: !capset!(), effective: !capset!(Cap::CHOWN), inheritable: !capset!(), } ); assert_eq!( caps_from_text("=eip cap_chown-eip").unwrap(), CapState { permitted: !capset!(Cap::CHOWN), effective: !capset!(Cap::CHOWN), inheritable: !capset!(Cap::CHOWN), } ); } } capctl-0.2.4/src/caps/capset.rs000064400000000000000000000503731046102023000144470ustar 00000000000000use core::fmt; use core::iter::FromIterator; use core::ops::{ BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub, SubAssign, }; use super::{Cap, CAP_BITMASK, NUM_CAPS}; /// Represents a set of capabilities. /// /// Internally, this stores the set of capabilities as a bitmask, which is much more efficient than /// a `HashSet`. #[derive(Copy, Clone, Eq, Hash, PartialEq)] pub struct CapSet { pub(super) bits: u64, } impl CapSet { /// Create an empty capability set. #[inline] pub const fn empty() -> Self { Self { bits: 0 } } /// Clear all capabilities from this set. /// /// After this call, `set.is_empty()` will return `true`. #[inline] pub fn clear(&mut self) { self.bits = 0; } /// Check if this capability set is empty. #[inline] pub fn is_empty(&self) -> bool { self.bits == 0 } /// Returns the number of capabilities in this capability set. #[inline] pub fn size(&self) -> usize { self.bits.count_ones() as usize } /// Checks if a given capability is present in this set. #[inline] pub fn has(&self, cap: Cap) -> bool { self.bits & cap.to_single_bitfield() != 0 } /// Adds the given capability to this set. #[inline] pub fn add(&mut self, cap: Cap) { self.bits |= cap.to_single_bitfield(); } /// Removes the given capability from this set. #[inline] pub fn drop(&mut self, cap: Cap) { self.bits &= !cap.to_single_bitfield(); } /// If `val` is `true` the given capability is added; otherwise it is removed. pub fn set_state(&mut self, cap: Cap, val: bool) { if val { self.add(cap); } else { self.drop(cap); } } /// Adds all of the capabilities yielded by the given iterator to this set. /// /// If you want to add all the capabilities in another `CapSet`, you should use /// `set1 = set1.union(set2)` or `set1 |= set2`, NOT `set1.add_all(set2)`. pub fn add_all>(&mut self, t: T) { for cap in t.into_iter() { self.add(cap); } } /// Removes all of the capabilities yielded by the given iterator from this set. /// /// If you want to remove all the capabilities in another `CapSet`, you should use /// `set1 = set1.intersection(!set2)`, `set1 &= !set2`, or `set1 -= set2`, NOT /// `set1.drop_all(set2)`. pub fn drop_all>(&mut self, t: T) { for cap in t.into_iter() { self.drop(cap); } } /// Returns an iterator over all of the capabilities in this set. #[inline] pub fn iter(&self) -> CapSetIterator { self.into_iter() } /// Returns the union of this set and another capability set (i.e. all the capabilities that /// are in either set). #[inline] pub const fn union(&self, other: Self) -> Self { Self { bits: self.bits | other.bits, } } /// Returns the intersection of this set and another capability set (i.e. all the capabilities /// that are in both sets). #[inline] pub const fn intersection(&self, other: Self) -> Self { Self { bits: self.bits & other.bits, } } /// Return whether this set is a subset of another capability set. /// /// This returns `true` if all of the capabilities in `self` are present in `other`. #[inline] pub const fn issubset(&self, other: Self) -> bool { (self.bits & !other.bits) == 0 } /// Return whether this set is a superset of another capability set. /// /// This returns `true` if all of the capabilities in `other` are present in `self`. #[inline] pub const fn issuperset(&self, other: Self) -> bool { other.issubset(*self) } /// WARNING: This is an internal method and its signature may change in the future. Use [the /// `capset!()` macro] instead. /// /// [the `capset!()` macro]: ../macro.capset.html #[doc(hidden)] #[inline] pub const fn from_bitmask_truncate(bitmask: u64) -> Self { Self { bits: bitmask & CAP_BITMASK, } } #[inline] pub(crate) fn from_bitmasks_u32(lower: u32, upper: u32) -> Self { Self::from_bitmask_truncate(((upper as u64) << 32) | (lower as u64)) } } impl Default for CapSet { #[inline] fn default() -> Self { Self::empty() } } impl Not for CapSet { type Output = Self; #[inline] fn not(self) -> Self::Output { Self { bits: (!self.bits) & CAP_BITMASK, } } } impl BitAnd for CapSet { type Output = Self; #[inline] fn bitand(self, rhs: Self) -> Self::Output { self.intersection(rhs) } } impl BitOr for CapSet { type Output = Self; #[inline] fn bitor(self, rhs: Self) -> Self::Output { self.union(rhs) } } impl BitXor for CapSet { type Output = Self; #[inline] fn bitxor(self, rhs: Self) -> Self::Output { Self { bits: self.bits ^ rhs.bits, } } } impl Sub for CapSet { type Output = Self; #[inline] fn sub(self, rhs: Self) -> Self::Output { Self { bits: self.bits & (!rhs.bits), } } } impl BitAndAssign for CapSet { #[inline] fn bitand_assign(&mut self, rhs: Self) { *self = *self & rhs; } } impl BitOrAssign for CapSet { #[inline] fn bitor_assign(&mut self, rhs: Self) { *self = *self | rhs; } } impl BitXorAssign for CapSet { #[inline] fn bitxor_assign(&mut self, rhs: Self) { *self = *self ^ rhs; } } impl SubAssign for CapSet { #[inline] fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs; } } impl Extend for CapSet { #[inline] fn extend>(&mut self, it: I) { self.add_all(it); } } impl FromIterator for CapSet { #[inline] fn from_iter>(it: I) -> Self { let mut res = Self::empty(); res.extend(it); res } } impl IntoIterator for CapSet { type Item = Cap; type IntoIter = CapSetIterator; #[inline] fn into_iter(self) -> Self::IntoIter { CapSetIterator { set: self, i: 0 } } } impl fmt::Debug for CapSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_set().entries(self.iter()).finish() } } /// A helper macro to statically construct a `CapSet` from a list of capabilities. /// /// Examples: /// ``` /// # use core::iter::FromIterator; /// # use capctl::capset; /// # use capctl::caps::{Cap, CapSet}; /// assert_eq!(capset!(), CapSet::empty()); /// assert_eq!(capset!(Cap::CHOWN), [Cap::CHOWN].iter().cloned().collect()); /// assert_eq!(capset!(Cap::CHOWN, Cap::SYSLOG), [Cap::CHOWN, Cap::SYSLOG].iter().cloned().collect()); /// /// const CAPS: CapSet = capset!(Cap::CHOWN); /// ``` /// /// Note that you cannot use raw integers, only `Cap` variants. For example, this is not allowed: /// /// ```compile_fail /// # use capctl::capset; /// # use capctl::caps::{Cap, CapSet}; /// assert_eq!(capset!(0), capset!(Cap::CHOWN)); /// ``` #[macro_export] macro_rules! capset { () => { $crate::caps::CapSet::empty() }; ($($caps:expr),+ $(,)?) => { $crate::caps::CapSet::from_bitmask_truncate( 0 $(| (1u64 << ($caps as $crate::caps::Cap as u8)))* ) }; } /// An iterator over all the capabilities in a `CapSet`. /// /// This is constructed by [`CapSet::iter()`]. /// /// [`CapSet::iter()`]: ./struct.CapSet.html#method.iter #[derive(Clone)] pub struct CapSetIterator { set: CapSet, i: u8, } impl Iterator for CapSetIterator { type Item = Cap; fn next(&mut self) -> Option { while let Some(cap) = Cap::from_u8(self.i) { self.i += 1; if self.set.has(cap) { return Some(cap); } } None } #[inline] fn last(self) -> Option { // This calculates the position of the largest bit that is set. // For example, if the bitmask is 0b10101, n=5. let n = core::mem::size_of::() as u8 * 8 - self.set.bits.leading_zeros() as u8; if self.i < n { // We haven't yet passed the largest bit. // This uses `<` instead of `<=` because `self.i` and `n` are off by 1 (so we also have // to subtract 1 below). let res = Cap::from_u8(n - 1); debug_assert!(res.is_some()); res } else { None } } #[inline] fn count(self) -> usize { self.len() } #[inline] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl ExactSizeIterator for CapSetIterator { #[inline] fn len(&self) -> usize { // It should be literally impossible for i to be out of this range debug_assert!(self.i <= NUM_CAPS); (self.set.bits >> self.i).count_ones() as usize } } impl core::iter::FusedIterator for CapSetIterator {} #[cfg(test)] mod tests { use super::*; #[test] fn test_capset_empty() { let mut set = Cap::iter().collect::(); for cap in Cap::iter() { set.drop(cap); } assert_eq!(set.bits, 0); assert!(set.is_empty()); set = CapSet::empty(); assert_eq!(set.bits, 0); assert!(set.is_empty()); assert_eq!(set, CapSet::default()); set = Cap::iter().collect::(); set.clear(); assert_eq!(set.bits, 0); assert!(set.is_empty()); assert!(!Cap::iter().any(|c| set.has(c))); } #[test] fn test_capset_full() { let mut set = CapSet::empty(); for cap in Cap::iter() { set.add(cap); } assert_eq!(set.bits, CAP_BITMASK); assert!(!set.is_empty()); set = CapSet::empty(); set.extend(Cap::iter()); assert_eq!(set.bits, CAP_BITMASK); assert!(!set.is_empty()); assert!(Cap::iter().all(|c| set.has(c))); } #[test] fn test_capset_add_drop() { let mut set = CapSet::empty(); set.add(Cap::CHOWN); assert!(set.has(Cap::CHOWN)); assert!(!set.is_empty()); set.drop(Cap::CHOWN); assert!(!set.has(Cap::CHOWN)); assert!(set.is_empty()); set.set_state(Cap::CHOWN, true); assert!(set.has(Cap::CHOWN)); assert!(!set.is_empty()); set.set_state(Cap::CHOWN, false); assert!(!set.has(Cap::CHOWN)); assert!(set.is_empty()); } #[test] fn test_capset_add_drop_all() { let mut set = CapSet::empty(); set.add_all([Cap::FOWNER, Cap::CHOWN, Cap::KILL].iter().cloned()); // Iteration order is not preserved, but it should be consistent. assert!(set .into_iter() .eq([Cap::CHOWN, Cap::FOWNER, Cap::KILL].iter().cloned())); assert!(set .into_iter() .eq([Cap::CHOWN, Cap::FOWNER, Cap::KILL].iter().cloned())); set.drop_all([Cap::FOWNER, Cap::CHOWN].iter().cloned()); assert!(set.iter().eq([Cap::KILL].iter().cloned())); set.drop_all([Cap::KILL].iter().cloned()); assert!(set.iter().eq([].iter().cloned())); } #[test] fn test_capset_from_iter() { let set = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); assert!(set.iter().eq([Cap::CHOWN, Cap::FOWNER].iter().cloned())); } #[test] fn test_capset_iter_full() { assert!(Cap::iter().eq(CapSet { bits: CAP_BITMASK }.iter())); assert!(Cap::iter().eq(Cap::iter().collect::().iter())); } #[test] fn test_capset_iter_count() { for set in [ [].iter().cloned().collect(), [Cap::CHOWN, Cap::FOWNER].iter().cloned().collect(), Cap::iter().collect::(), ] .iter() { let mut count = set.size(); let mut it = set.iter(); assert_eq!(it.len(), count); assert_eq!(it.clone().count(), count); assert_eq!(it.size_hint(), (count, Some(count))); while let Some(_cap) = it.next() { count -= 1; assert_eq!(it.len(), count); assert_eq!(it.clone().count(), count); assert_eq!(it.size_hint(), (count, Some(count))); } assert_eq!(count, 0); assert_eq!(it.len(), 0); assert_eq!(it.clone().count(), 0); assert_eq!(it.size_hint(), (0, Some(0))); } } #[test] fn test_capset_iter_last() { let last_cap = Cap::iter().last().unwrap(); assert_eq!( Cap::iter().collect::().iter().last(), Some(last_cap) ); assert_eq!(CapSet::empty().iter().last(), None); let mut it = Cap::iter().collect::().iter(); assert_eq!(it.clone().last(), Some(last_cap)); while it.next().is_some() { if it.clone().next().is_some() { assert_eq!(it.clone().last(), Some(last_cap)); } else { assert_eq!(it.clone().last(), None); } } assert_eq!(it.len(), 0); assert_eq!(it.last(), None); it = capset!(Cap::FOWNER).iter(); assert_eq!(it.clone().last(), Some(Cap::FOWNER)); assert_eq!(it.next(), Some(Cap::FOWNER)); assert_eq!(it.last(), None); it = capset!(Cap::CHOWN).iter(); assert_eq!(it.clone().last(), Some(Cap::CHOWN)); assert_eq!(it.next(), Some(Cap::CHOWN)); assert_eq!(it.last(), None); it = capset!(Cap::CHOWN, Cap::FOWNER).iter(); assert_eq!(it.clone().last(), Some(Cap::FOWNER)); assert_eq!(it.next(), Some(Cap::CHOWN)); assert_eq!(it.clone().last(), Some(Cap::FOWNER)); assert_eq!(it.next(), Some(Cap::FOWNER)); assert_eq!(it.clone().last(), None); assert_eq!(it.next(), None); assert_eq!(it.last(), None); } #[test] fn test_capset_union() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::CHOWN, Cap::FOWNER, Cap::KILL] .iter() .cloned() .collect::(); assert_eq!(a.union(b), c); } #[test] fn test_capset_intersection() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::FOWNER].iter().cloned().collect::(); assert_eq!(a.intersection(b), c); } #[test] fn test_capset_not() { assert_eq!(!Cap::iter().collect::(), CapSet::empty()); assert_eq!(Cap::iter().collect::(), !CapSet::empty()); let mut a = Cap::iter().collect::(); let mut b = CapSet::empty(); a.add(Cap::CHOWN); b.drop(Cap::CHOWN); assert_eq!(!a, b); } #[test] fn test_capset_bitor() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::CHOWN, Cap::FOWNER, Cap::KILL] .iter() .cloned() .collect::(); assert_eq!(a | b, c); let mut d = a; d |= b; assert_eq!(d, c); } #[test] fn test_capset_bitand() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::FOWNER].iter().cloned().collect::(); assert_eq!(a & b, c); let mut d = a; d &= b; assert_eq!(d, c); } #[test] fn test_capset_bitxor() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::CHOWN, Cap::KILL].iter().cloned().collect::(); assert_eq!(a ^ b, c); let mut d = a; d ^= b; assert_eq!(d, c); } #[test] fn test_capset_sub() { let a = [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::(); let b = [Cap::FOWNER, Cap::KILL].iter().cloned().collect::(); let c = [Cap::CHOWN].iter().cloned().collect::(); assert_eq!(a - b, c); let mut d = a; d -= b; assert_eq!(d, c); } #[cfg(feature = "std")] #[test] fn test_capset_fmt() { assert_eq!(format!("{:?}", CapSet::empty()), "{}"); assert_eq!( format!("{:?}", [Cap::CHOWN].iter().cloned().collect::()), "{CHOWN}" ); assert_eq!( format!( "{:?}", [Cap::CHOWN, Cap::FOWNER] .iter() .cloned() .collect::() ), "{CHOWN, FOWNER}" ); } #[test] fn test_capset_macro() { assert_eq!(capset!(), CapSet::empty()); assert_eq!(capset!(Cap::CHOWN), [Cap::CHOWN].iter().cloned().collect()); assert_eq!(capset!(Cap::CHOWN,), [Cap::CHOWN].iter().cloned().collect()); for cap in Cap::iter() { assert_eq!(capset!(cap), [cap].iter().cloned().collect()); assert_eq!(capset!(cap,), [cap].iter().cloned().collect()); } assert_eq!( capset!(Cap::CHOWN, Cap::SYSLOG), [Cap::CHOWN, Cap::SYSLOG].iter().cloned().collect() ); assert_eq!( capset!(Cap::CHOWN, Cap::SYSLOG,), [Cap::CHOWN, Cap::SYSLOG].iter().cloned().collect() ); assert_eq!( capset!(Cap::CHOWN, Cap::SYSLOG, Cap::FOWNER), [Cap::CHOWN, Cap::SYSLOG, Cap::FOWNER] .iter() .cloned() .collect() ); assert_eq!( capset!(Cap::CHOWN, Cap::SYSLOG, Cap::FOWNER,), [Cap::CHOWN, Cap::SYSLOG, Cap::FOWNER] .iter() .cloned() .collect() ); const EMPTY_SET: CapSet = capset!(); assert_eq!(EMPTY_SET, CapSet::empty()); const CHOWN_SET: CapSet = capset!(Cap::CHOWN); assert_eq!(CHOWN_SET, [Cap::CHOWN].iter().cloned().collect()); const CHOWN_SYSLOG_SET: CapSet = capset!(Cap::CHOWN, Cap::SYSLOG,); assert_eq!( CHOWN_SYSLOG_SET, [Cap::CHOWN, Cap::SYSLOG].iter().cloned().collect() ); } #[test] fn test_capset_subset() { assert!(capset!().issubset(capset!())); assert!(capset!().issubset(capset!(Cap::CHOWN))); assert!(capset!().issubset(capset!(Cap::CHOWN, Cap::SYSLOG))); assert!(capset!(Cap::CHOWN).issubset(capset!(Cap::CHOWN, Cap::SYSLOG))); assert!(capset!(Cap::SYSLOG).issubset(capset!(Cap::CHOWN, Cap::SYSLOG))); assert!(!capset!(Cap::CHOWN).issubset(capset!())); assert!(!capset!(Cap::CHOWN).issubset(capset!(Cap::SYSLOG))); assert!(!capset!(Cap::CHOWN, Cap::SYSLOG).issubset(capset!(Cap::CHOWN))); assert!(!capset!(Cap::CHOWN, Cap::SYSLOG).issubset(capset!(Cap::SYSLOG))); } #[test] fn test_capset_superset() { assert!(capset!().issuperset(capset!())); assert!(capset!(Cap::CHOWN).issuperset(capset!())); assert!(capset!(Cap::CHOWN, Cap::SYSLOG).issuperset(capset!())); assert!(capset!(Cap::CHOWN, Cap::SYSLOG).issuperset(capset!(Cap::CHOWN))); assert!(capset!(Cap::CHOWN, Cap::SYSLOG).issuperset(capset!(Cap::SYSLOG))); assert!(!capset!().issuperset(capset!(Cap::CHOWN))); assert!(!capset!(Cap::SYSLOG).issuperset(capset!(Cap::CHOWN))); assert!(!capset!(Cap::CHOWN).issuperset(capset!(Cap::CHOWN, Cap::SYSLOG))); assert!(!capset!(Cap::SYSLOG).issuperset(capset!(Cap::CHOWN, Cap::SYSLOG))); } } capctl-0.2.4/src/caps/capstate.rs000064400000000000000000000274221046102023000147730ustar 00000000000000use core::fmt; use crate::sys; use super::cap_text::{caps_from_text, caps_to_text, ParseCapsError}; use super::CapSet; /// Represents the permitted, effective, and inheritable capability sets of a thread. /// /// # `FromStr` and `Display` implementations /// /// This struct's implementations of `FromStr` and `Display` use the same format as `libcap`'s /// `cap_from_text()` and `cap_to_text()`. For example, an empty state can be represented as `=`, a /// "full" state can be represented as `=eip`, and a state containing only `CAP_CHOWN` in the /// effective and permitted sets can be represented by `cap_chown=ep`. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct CapState { pub effective: CapSet, pub permitted: CapSet, pub inheritable: CapSet, } impl CapState { /// Construct an empty `CapState` object. #[inline] pub fn empty() -> Self { Self { effective: CapSet::empty(), permitted: CapSet::empty(), inheritable: CapSet::empty(), } } /// Get the capability state of the current thread. /// /// This is equivalent to `CapState::get_for_pid(0)`. #[inline] pub fn get_current() -> crate::Result { Self::get_for_pid(0) } /// Get the capability state of the process (or thread) with the given PID (or TID). /// /// If `pid` is 0, this method gets the capability state of the current thread. pub fn get_for_pid(pid: libc::pid_t) -> crate::Result { let mut header = sys::cap_user_header_t { version: sys::_LINUX_CAPABILITY_VERSION_3, pid: pid as libc::c_int, }; let mut raw_dat = [sys::cap_user_data_t { effective: 0, permitted: 0, inheritable: 0, }; 2]; cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(unsafe { sc::syscall!(CAPGET, &mut header as *mut _, raw_dat.as_mut_ptr()) })?; } else { if unsafe { sys::capget(&mut header, raw_dat.as_mut_ptr()) } < 0 { return Err(crate::Error::last()); } } } Ok(Self { effective: CapSet::from_bitmasks_u32(raw_dat[0].effective, raw_dat[1].effective), permitted: CapSet::from_bitmasks_u32(raw_dat[0].permitted, raw_dat[1].permitted), inheritable: CapSet::from_bitmasks_u32(raw_dat[0].inheritable, raw_dat[1].inheritable), }) } /// Set the current capability state to the state represented by this object. pub fn set_current(&self) -> crate::Result<()> { let mut header = sys::cap_user_header_t { version: sys::_LINUX_CAPABILITY_VERSION_3, pid: 0, }; let effective = self.effective.bits; let permitted = self.permitted.bits; let inheritable = self.inheritable.bits; let raw_dat = [ sys::cap_user_data_t { effective: effective as u32, permitted: permitted as u32, inheritable: inheritable as u32, }, sys::cap_user_data_t { effective: (effective >> 32) as u32, permitted: (permitted >> 32) as u32, inheritable: (inheritable >> 32) as u32, }, ]; cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(unsafe { sc::syscall!(CAPSET, &mut header as *mut _, raw_dat.as_ptr()) })?; } else { if unsafe { sys::capset(&mut header, raw_dat.as_ptr()) } < 0 { return Err(crate::Error::last()); } } } Ok(()) } } impl fmt::Display for CapState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { caps_to_text(*self, f) } } impl core::str::FromStr for CapState { type Err = ParseCapStateError; #[inline] fn from_str(s: &str) -> Result { caps_from_text(s).map_err(ParseCapStateError) } } /// Represents an error when parsing a `CapState` object from a string. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ParseCapStateError(ParseCapsError); impl fmt::Display for ParseCapStateError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] impl std::error::Error for ParseCapStateError {} #[cfg(test)] mod tests { use super::*; use core::str::FromStr; use crate::caps::Cap; use crate::capset; #[test] fn test_capstate_empty() { assert_eq!( CapState::empty(), CapState { effective: CapSet::empty(), permitted: CapSet::empty(), inheritable: CapSet::empty(), } ); } #[test] fn test_capstate_getset_current() { let state = CapState::get_current().unwrap(); assert_eq!(state, CapState::get_for_pid(0).unwrap()); assert_eq!( state, CapState::get_for_pid(unsafe { libc::getpid() }).unwrap() ); state.set_current().unwrap(); } #[test] fn test_capstate_get_bad_pid() { assert_eq!(CapState::get_for_pid(-1).unwrap_err().code(), libc::EINVAL); assert_eq!( CapState::get_for_pid(libc::pid_t::MAX).unwrap_err().code(), libc::ESRCH ); } #[test] fn test_capstate_parse() { // caps_from_text() has more extensive tests; we can be a little loose here assert_eq!( CapState::from_str("cap_chown=eip cap_chown-p cap_syslog+p").unwrap(), CapState { permitted: capset!(Cap::SYSLOG), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), } ); #[cfg(feature = "std")] assert_eq!( CapState::from_str("cap_noexist+p").unwrap_err().to_string(), "Unknown capability" ); } #[cfg(feature = "std")] #[test] fn test_capstate_display() { // caps_to_text() has no tests in cap_text.rs, so we need to be rigorous assert_eq!(CapState::empty().to_string(), "="); assert_eq!( CapState { permitted: !capset!(), effective: !capset!(), inheritable: capset!(), } .to_string(), "=ep", ); assert_eq!( CapState { permitted: !capset!(), effective: !capset!(), inheritable: !capset!(), } .to_string(), "=eip", ); assert_eq!( CapState { permitted: capset!(Cap::CHOWN), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), } .to_string(), "cap_chown=eip", ); assert_eq!( CapState { permitted: capset!(Cap::CHOWN), effective: capset!(Cap::CHOWN), inheritable: capset!(), } .to_string(), "cap_chown=ep", ); assert_eq!( CapState { permitted: !capset!(Cap::CHOWN), effective: !capset!(Cap::CHOWN), inheritable: capset!(), } .to_string(), "=ep cap_chown-ep", ); for state in [ CapState::empty(), CapState { permitted: !capset!(), effective: !capset!(), inheritable: !capset!(), }, CapState { permitted: !capset!(), effective: capset!(), inheritable: capset!(), }, CapState { permitted: !capset!(Cap::CHOWN), effective: capset!(), inheritable: capset!(), }, CapState { permitted: capset!(), effective: !capset!(Cap::CHOWN), inheritable: capset!(), }, CapState { permitted: capset!(), effective: capset!(), inheritable: !capset!(Cap::CHOWN), }, CapState { permitted: !capset!(Cap::CHOWN), effective: capset!(Cap::CHOWN), inheritable: capset!(), }, CapState { permitted: capset!(Cap::CHOWN), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), }, CapState { permitted: capset!(Cap::SYSLOG), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), }, CapState { permitted: capset!(Cap::SYSLOG, Cap::CHOWN), effective: capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), }, CapState { permitted: capset!(Cap::SYSLOG, Cap::CHOWN), effective: capset!(Cap::SYSLOG, Cap::CHOWN), inheritable: capset!(Cap::SYSLOG, Cap::CHOWN), }, CapState { permitted: capset!(), effective: capset!(), inheritable: capset!(Cap::SYSLOG, Cap::CHOWN), }, CapState { permitted: !capset!(), effective: !capset!(), inheritable: capset!(Cap::CHOWN), }, CapState { permitted: !capset!(), effective: capset!(Cap::CHOWN), inheritable: !capset!(), }, CapState { permitted: !capset!(), effective: capset!(Cap::CHOWN), inheritable: capset!(), }, CapState { permitted: !capset!(), effective: !capset!(), inheritable: capset!( Cap::CHOWN, Cap::DAC_OVERRIDE, Cap::DAC_READ_SEARCH, Cap::FOWNER, Cap::FSETID, Cap::KILL, Cap::SETGID, Cap::SETUID, Cap::SETPCAP, Cap::LINUX_IMMUTABLE, Cap::NET_BIND_SERVICE, Cap::NET_BROADCAST, Cap::NET_ADMIN, Cap::NET_RAW, ), }, // Let's try some real-world data CapState::get_current().unwrap(), CapState::get_for_pid(1).unwrap(), ] .iter() { let s = state.to_string(); assert_eq!(s.parse::().unwrap(), *state, "{:?}", s); } } #[cfg(feature = "std")] #[test] fn test_capstate_display_names() { // cap_text::caps_to_text() uses a static buffer to hold the capability names, and if a new // capability with a long name is added then it could overflow that buffer. This test // ensures that won't happen by forcing caps_to_text() to print out all the capability // names and make sure they all print properly. for cap in Cap::iter() { let state = CapState { permitted: capset!(cap), effective: capset!(cap), inheritable: capset!(cap), }; let expected = format!("cap_{}=eip", cap.name().to_lowercase()); assert_eq!(state.to_string(), expected); } } } capctl-0.2.4/src/caps/file.rs000064400000000000000000000456361046102023000141150ustar 00000000000000use std::convert::TryInto; use std::ffi::{CString, OsStr}; use std::fmt; use std::io; use std::os::unix::prelude::*; use crate::sys; use super::cap_text::{caps_from_text, caps_to_text, ParseCapsError}; use super::{CapSet, CapState}; /// Represents the capabilities attached to a file. /// /// # `FromStr` and `Display` implementations /// /// Like [`CapState`], this struct's implementations of `FromStr` and `Display` use the same format /// as `libcap`'s `cap_from_text()` and `cap_to_text()`. See `CapState`'s [documentation on /// this](./struct.CapState.html#fromstr-and-display-implementations) for more details. /// /// [`CapState`]: ./struct.CapState.html #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub struct FileCaps { /// The "effective" bit. If this is set on a file, then during an `execve()` the kernel will /// raise all the capabilities from the file's `permitted` set in the process's new effective /// capability set. pub effective: bool, /// The permitted capability set. These capabilities are automatically added to the process's /// new permitted capability set. pub permitted: CapSet, /// The inheritable capability set. Any of these capabilities that are also present in the /// process's inheritable capability set before an `execve()` are added to the new process's /// permitted capability set. pub inheritable: CapSet, /// The root user ID of the user namespace in which file capabilities were added to this file. /// See capabilities(7) for more details. This is only set to a non-`None` value for version 3 /// file capabilities. pub rootid: Option, } impl FileCaps { /// Construct an empty `FileCaps` object. #[inline] pub fn empty() -> Self { Self { effective: false, permitted: CapSet::empty(), inheritable: CapSet::empty(), rootid: None, } } /// Get the file capabilities attached to the file identified by `path`. /// /// If an error occurs while retrieving information on the capabilities from the given file, /// this method returns `Err()`. Otherwise, if the given file has no file capabilities /// attached, this method returns `Ok(None)`. Otherwise, this method returns /// `Ok(Some())`. pub fn get_for_file>(path: P) -> io::Result> { let mut data = [0; sys::XATTR_CAPS_MAX_SIZE]; let path = CString::new(path.as_ref().as_bytes())?; let ret = unsafe { libc::getxattr( path.as_ptr(), sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char, data.as_mut_ptr() as *mut libc::c_void, data.len(), ) }; Self::extract_attr_or_error(&data, ret) } /// Get the file capabilities attached to the open file identified by the file descriptor `fd`. /// /// See [`get_for_file()`](#method.get_for_file) for more information. pub fn get_for_fd(fd: RawFd) -> io::Result> { let mut data = [0; sys::XATTR_CAPS_MAX_SIZE]; let ret = unsafe { libc::fgetxattr( fd, sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char, data.as_mut_ptr() as *mut libc::c_void, data.len(), ) }; Self::extract_attr_or_error(&data, ret) } fn extract_attr_or_error(data: &[u8], attr_res: isize) -> io::Result> { if attr_res >= 0 { Ok(Some(Self::unpack_attrs(&data[..(attr_res as usize)])?)) } else { let err = io::Error::last_os_error(); if err.raw_os_error() == Some(libc::ENODATA) { Ok(None) } else { Err(err) } } } /// From the raw data from the `security.capability` extended attribute of a file, construct a /// new `FileCaps` object representing the same data. /// /// Most users should call [`get_for_file()`] or [`get_for_fd()`]; those methods call this /// method internally. /// /// [`get_for_file()`]: #method.get_for_file /// [`get_for_fd()`]: #method.get_for_fd pub fn unpack_attrs(attrs: &[u8]) -> io::Result { let len = attrs.len(); if len < 4 { return Err(io::Error::from_raw_os_error(libc::EINVAL)); } let magic = u32::from_le_bytes(attrs[0..4].try_into().unwrap()); let version = magic & sys::VFS_CAP_REVISION_MASK; let flags = magic & sys::VFS_CAP_FLAGS_MASK; let effective = (flags & sys::VFS_CAP_FLAGS_EFFECTIVE) != 0; match (version, len) { (sys::VFS_CAP_REVISION_2, sys::XATTR_CAPS_SZ_2) => Ok(FileCaps { effective, permitted: CapSet::from_bitmasks_u32( u32::from_le_bytes(attrs[4..8].try_into().unwrap()), u32::from_le_bytes(attrs[12..16].try_into().unwrap()), ), inheritable: CapSet::from_bitmasks_u32( u32::from_le_bytes(attrs[8..12].try_into().unwrap()), u32::from_le_bytes(attrs[16..20].try_into().unwrap()), ), rootid: None, }), (sys::VFS_CAP_REVISION_3, sys::XATTR_CAPS_SZ_3) => Ok(FileCaps { effective, permitted: CapSet::from_bitmasks_u32( u32::from_le_bytes(attrs[4..8].try_into().unwrap()), u32::from_le_bytes(attrs[12..16].try_into().unwrap()), ), inheritable: CapSet::from_bitmasks_u32( u32::from_le_bytes(attrs[8..12].try_into().unwrap()), u32::from_le_bytes(attrs[16..20].try_into().unwrap()), ), rootid: Some(u32::from_le_bytes(attrs[20..24].try_into().unwrap())), }), (sys::VFS_CAP_REVISION_1, sys::XATTR_CAPS_SZ_1) => Ok(FileCaps { effective, permitted: CapSet::from_bitmask_truncate(u32::from_le_bytes( attrs[4..8].try_into().unwrap(), ) as u64), inheritable: CapSet::from_bitmask_truncate(u32::from_le_bytes( attrs[8..12].try_into().unwrap(), ) as u64), rootid: None, }), (_, _) => Err(io::Error::from_raw_os_error(libc::EINVAL)), } } /// Set the file capabilities attached to the file identified by `path` to the state /// represented by this object. #[inline] pub fn set_for_file>(&self, path: P) -> io::Result<()> { let path = CString::new(path.as_ref().as_bytes())?; let mut buf = [0u8; sys::XATTR_CAPS_MAX_SIZE]; let len = self.pack_into(&mut buf); debug_assert!(len <= buf.len()); if unsafe { libc::setxattr( path.as_ptr(), sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char, buf.as_ptr() as *const libc::c_void, len, 0, ) } < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } /// Set the file capabilities attached to the open file identified by the file descriptor `fd` /// to the state represented by this object. #[inline] pub fn set_for_fd(&self, fd: RawFd) -> io::Result<()> { let mut buf = [0u8; sys::XATTR_CAPS_MAX_SIZE]; let len = self.pack_into(&mut buf); debug_assert!(len <= buf.len()); if unsafe { libc::fsetxattr( fd, sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char, buf.as_ptr() as *const libc::c_void, len, 0, ) } < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } fn pack_into(&self, buf: &mut [u8]) -> usize { let mut magic = if self.rootid.is_some() { sys::VFS_CAP_REVISION_3 } else { sys::VFS_CAP_REVISION_2 }; if self.effective { magic |= sys::VFS_CAP_FLAGS_EFFECTIVE; } let mut len = 20; buf[..4].copy_from_slice(&magic.to_le_bytes()); buf[4..8].copy_from_slice(&(self.permitted.bits as u32).to_le_bytes()); buf[8..12].copy_from_slice(&(self.inheritable.bits as u32).to_le_bytes()); buf[12..16].copy_from_slice(&((self.permitted.bits >> 32) as u32).to_le_bytes()); buf[16..20].copy_from_slice(&((self.inheritable.bits >> 32) as u32).to_le_bytes()); if let Some(rootid) = self.rootid { buf[len..len + 4].copy_from_slice(&rootid.to_le_bytes()); len += 4; } len } /// "Pack" the file capabilities represented by this object; i.e. convert it to the raw binary /// data as stored in the extended attribute. /// /// **Note**: Most users should call [`set_for_file()`] or [`set_for_fd()`]; those methods /// handle the details of "packing" file capabilities internally. /// /// This is the reverse of [`unpack_attrs()`]. As a result, "packing" the object using this /// method and then "unpacking" it using `unpack_attrs()` should always return a `FileCaps` /// object that represents the same state. So: /// /// ``` /// # use capctl::caps::FileCaps; /// let fcaps = FileCaps::empty(); /// assert_eq!(FileCaps::unpack_attrs(&fcaps.pack_attrs()).unwrap(), fcaps); /// ``` /// /// (Note, however, that the reverse is not always true. For example, version 1 file /// capabilities can be "unpacked", but they will be "packed" as version 2 file capabilities, /// and as a result the binary data will be different.) /// /// [`set_for_file()`]: #method.set_for_file /// [`set_for_fd()`]: #method.set_for_fd /// [`unpack_attrs()`]: #method.unpack_attrs #[inline] pub fn pack_attrs(&self) -> Vec { let mut buf = vec![0u8; sys::XATTR_CAPS_MAX_SIZE]; let len = self.pack_into(&mut buf); buf.truncate(len); buf } /// Remove the file capabilities attached to the file identified by `path`. #[inline] pub fn remove_for_file>(path: P) -> io::Result<()> { let path = CString::new(path.as_ref().as_bytes())?; if unsafe { libc::removexattr( path.as_ptr(), sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char, ) } < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } /// Remove the file capabilities attached to the open file identified by `fd`. #[inline] pub fn remove_for_fd(fd: RawFd) -> io::Result<()> { if unsafe { libc::fremovexattr(fd, sys::XATTR_NAME_CAPS.as_ptr() as *const libc::c_char) } < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } impl fmt::Display for FileCaps { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { caps_to_text( CapState { effective: if self.effective { self.permitted } else { CapSet::empty() }, permitted: self.permitted, inheritable: self.inheritable, }, f, ) } } impl core::str::FromStr for FileCaps { type Err = ParseFileCapsError; #[inline] fn from_str(s: &str) -> Result { match caps_from_text(s) { Ok(state) => { if !state.effective.is_empty() && state.effective != state.permitted { return Err(ParseFileCapsError(ParseCapsError::BadFileEffective)); } Ok(Self { effective: !state.effective.is_empty(), permitted: state.permitted, inheritable: state.inheritable, rootid: None, }) } Err(e) => Err(ParseFileCapsError(e)), } } } /// Represents an error when parsing a `FileCaps` object from a string. #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ParseFileCapsError(ParseCapsError); impl fmt::Display for ParseFileCapsError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl std::error::Error for ParseFileCapsError {} #[cfg(test)] mod tests { use core::iter::FromIterator; use core::str::FromStr; use crate::caps::Cap; use crate::capset; use super::*; #[test] fn test_filecaps_empty() { let empty_caps = FileCaps::empty(); assert!(!empty_caps.effective); assert!(empty_caps.permitted.is_empty()); assert!(empty_caps.inheritable.is_empty()); assert!(empty_caps.rootid.is_none()); } #[test] fn test_filecaps_get() { let current_exe = std::env::current_exe().unwrap(); FileCaps::get_for_file(¤t_exe).unwrap(); let f = std::fs::File::open(¤t_exe).unwrap(); FileCaps::get_for_fd(f.as_raw_fd()).unwrap(); assert_eq!( FileCaps::get_for_file(current_exe.join("sub")) .unwrap_err() .raw_os_error(), Some(libc::ENOTDIR) ); assert_eq!( FileCaps::get_for_fd(-1).unwrap_err().raw_os_error(), Some(libc::EBADF) ); } #[test] fn test_filecaps_pack_unpack() { assert_eq!( FileCaps::unpack_attrs(b"").unwrap_err().raw_os_error(), Some(libc::EINVAL) ); assert_eq!( FileCaps::unpack_attrs(b"\x00\x00\x00") .unwrap_err() .raw_os_error(), Some(libc::EINVAL) ); assert_eq!( FileCaps::unpack_attrs(b"\x00\x00\x00\x00") .unwrap_err() .raw_os_error(), Some(libc::EINVAL) ); // Version 1 assert_eq!( FileCaps::unpack_attrs(b"\x00\x00\x00\x01\x01\x00\x00\x00\x01\x00\x00\x00").unwrap(), FileCaps { effective: false, permitted: CapSet::from_iter(vec![Cap::CHOWN]), inheritable: CapSet::from_iter(vec![Cap::CHOWN]), rootid: None, }, ); // Round-tripping Version 2 and Version 3 capabilities for (attr_data, fcaps) in [ // Version 2 (real example, from Wireshark's /usr/bin/dumpcap) ( b"\x01\x00\x00\x02\x020\x00\x00\x020\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".as_ref(), FileCaps { effective: true, permitted: CapSet::from_iter(vec![Cap::DAC_OVERRIDE, Cap::NET_ADMIN, Cap::NET_RAW]), inheritable: CapSet::from_iter(vec![ Cap::DAC_OVERRIDE, Cap::NET_ADMIN, Cap::NET_RAW ]), rootid: None, } ), // Version 3 ( b"\x00\x00\x00\x03\x020\x00\x00\x020\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\xe8\x03\x00\x00".as_ref(), FileCaps { effective: false, permitted: CapSet::from_iter(vec![Cap::DAC_OVERRIDE, Cap::NET_ADMIN, Cap::NET_RAW, Cap::SYSLOG]), inheritable: CapSet::from_iter(vec![Cap::DAC_OVERRIDE, Cap::NET_ADMIN, Cap::NET_RAW, Cap::WAKE_ALARM]), rootid: Some(1000), } ), ].iter() { assert_eq!(FileCaps::unpack_attrs(attr_data).unwrap(), *fcaps); assert_eq!(&fcaps.pack_attrs(), attr_data); } } #[test] fn test_filecaps_set_error() { let current_exe = std::env::current_exe().unwrap(); assert_eq!( FileCaps::empty() .set_for_file(current_exe.join("sub")) .unwrap_err() .raw_os_error(), Some(libc::ENOTDIR) ); assert_eq!( FileCaps::empty().set_for_fd(-1).unwrap_err().raw_os_error(), Some(libc::EBADF) ); } #[test] fn test_filecaps_remove_error() { let current_exe = std::env::current_exe().unwrap(); assert_eq!( FileCaps::remove_for_file(current_exe.join("sub")) .unwrap_err() .raw_os_error(), Some(libc::ENOTDIR) ); assert_eq!( FileCaps::remove_for_fd(-1).unwrap_err().raw_os_error(), Some(libc::EBADF) ); } #[test] fn test_filecaps_parse() { // caps_from_text() has more extensive tests; we can be a little loose here assert_eq!( FileCaps::from_str("cap_chown=eip cap_chown-i cap_syslog+i").unwrap(), FileCaps { effective: true, permitted: capset!(Cap::CHOWN), inheritable: capset!(Cap::SYSLOG), rootid: None, } ); assert_eq!( FileCaps::from_str("cap_chown=p").unwrap(), FileCaps { effective: false, permitted: capset!(Cap::CHOWN), inheritable: capset!(), rootid: None, } ); assert_eq!( FileCaps::from_str("cap_chown=e").unwrap_err().to_string(), "Effective set must be either empty or same as permitted set", ); assert_eq!( FileCaps::from_str("cap_noexist+p").unwrap_err().to_string(), "Unknown capability" ); } #[test] fn test_filecaps_display() { for fcaps in [ FileCaps::empty(), FileCaps { effective: true, permitted: capset!(Cap::CHOWN), inheritable: capset!(Cap::SYSLOG), rootid: None, }, FileCaps { effective: false, permitted: capset!(Cap::CHOWN), inheritable: capset!(), rootid: None, }, FileCaps { effective: false, permitted: !capset!(Cap::CHOWN), inheritable: capset!(Cap::CHOWN), rootid: None, }, FileCaps { effective: false, permitted: !capset!(Cap::CHOWN), inheritable: !capset!(Cap::CHOWN), rootid: None, }, ] .iter() { let s = fcaps.to_string(); assert_eq!(s.parse::().unwrap(), *fcaps, "{:?}", s); } } } capctl-0.2.4/src/caps/fullcapstate.rs000064400000000000000000000116401046102023000156510ustar 00000000000000use std::fs; use std::io; use std::io::prelude::*; use super::{ambient, bounding, CapSet, CapState}; /// Represents the "full" capability state of a thread (i.e. the contents of all 5 capability /// sets and some additional information). #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] #[non_exhaustive] pub struct FullCapState { pub permitted: CapSet, pub effective: CapSet, pub inheritable: CapSet, pub ambient: CapSet, pub bounding: CapSet, pub no_new_privs: bool, } impl FullCapState { /// Construct an empty `FullCapState` object. #[inline] pub fn empty() -> Self { Self { permitted: CapSet::empty(), effective: CapSet::empty(), inheritable: CapSet::empty(), ambient: CapSet::empty(), bounding: CapSet::empty(), no_new_privs: false, } } /// Get the full capability state of the current thread. /// /// This is equivalent to `FullCapState::get_for_pid(0)`. However, this method uses the kernel /// APIs to retrieve information instead of examining files in `/proc`. pub fn get_current() -> io::Result { let state = CapState::get_current()?; Ok(Self { permitted: state.permitted, effective: state.effective, inheritable: state.inheritable, ambient: ambient::probe().unwrap_or_default(), bounding: bounding::probe(), no_new_privs: crate::prctl::get_no_new_privs()?, }) } /// Get the full capability state of the process (or thread) with the given PID (or TID) by /// examining special files in `/proc`. /// /// If `pid` is 0, this method gets the capability state of the current thread. pub fn get_for_pid(pid: libc::pid_t) -> io::Result { let file_res = match pid.cmp(&0) { core::cmp::Ordering::Less => return Err(io::Error::from_raw_os_error(libc::EINVAL)), core::cmp::Ordering::Equal => fs::File::open("/proc/thread-self/status"), core::cmp::Ordering::Greater => fs::File::open(format!("/proc/{}/status", pid)), }; let f = match file_res { Ok(f) => f, Err(e) if e.raw_os_error() == Some(libc::ENOENT) => { return Err(io::Error::from_raw_os_error(libc::ESRCH)); } Err(e) => return Err(e), }; let mut reader = io::BufReader::new(f); let mut line = String::new(); let mut res = Self::empty(); while reader.read_line(&mut line)? > 0 { if line.ends_with('\n') { line.pop(); } if let Some(i) = line.find(":\t") { let value = &line[i + 2..]; let set = match &line[..i] { "CapPrm" => Some(&mut res.permitted), "CapEff" => Some(&mut res.effective), "CapInh" => Some(&mut res.inheritable), "CapBnd" => Some(&mut res.bounding), "CapAmb" => Some(&mut res.ambient), "NoNewPrivs" => { res.no_new_privs = value == "1"; None } _ => None, }; if let Some(set) = set { match u64::from_str_radix(value, 16) { Ok(bitmask) => *set = CapSet::from_bitmask_truncate(bitmask), Err(e) => { return Err(io::Error::new(io::ErrorKind::Other, e.to_string())); } } } } line.clear(); } Ok(res) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_current_proc() { assert_eq!( FullCapState::get_current().unwrap(), FullCapState::get_for_pid(0).unwrap(), ); assert_eq!( FullCapState::get_current().unwrap(), FullCapState::get_for_pid(unsafe { libc::syscall(libc::SYS_gettid) } as libc::pid_t) .unwrap(), ); } #[test] fn test_get_invalid_pid() { assert_eq!( FullCapState::get_for_pid(-1).unwrap_err().raw_os_error(), Some(libc::EINVAL) ); assert_eq!( FullCapState::get_for_pid(libc::pid_t::MAX) .unwrap_err() .raw_os_error(), Some(libc::ESRCH) ); } #[test] fn test_pid_1_match() { let state = CapState::get_for_pid(1).unwrap(); let fullstate = FullCapState::get_for_pid(1).unwrap(); assert_eq!(state.effective, fullstate.effective); assert_eq!(state.permitted, fullstate.permitted); assert_eq!(state.inheritable, fullstate.inheritable); } } capctl-0.2.4/src/caps/helpers.rs000064400000000000000000000157661046102023000146410ustar 00000000000000use super::{Cap, CapState}; /// Set the current thread's UID/GID/supplementary groups while preserving permitted capabilities. /// /// This combines the functionality of ``libcap``'s ``cap_setuid()`` and ``cap_setgroups()``, while /// providing greater flexibility. /// /// WARNING: This function only operates on the current **thread**, not the process as a whole. This is /// because of the way Linux operates. If you call this function from a multithreaded program, you /// are responsible for synchronizing changes across threads as necessary to ensure proper security. /// /// This function performs the following actions in order. (Note: If `gid` is not `None` or /// `groups` is not `None`, CAP_SETGID will first be raised in the thread's effective set, and if /// `uid` is not `None` then CAP_SETUID will be raised.) /// /// - If `gid` is not `None`, the thread's real, effective and saved GIDs will be set to `gid`. /// - If `groups` is not `None`, the thread's supplementary group list will be set to `groups`. /// - If `uid` is not `None`, the thread's real, effective and saved UIDs will be set to `uid`. /// - The effective capability set will be emptied. /// /// Note: If this function fails and returns an error, the thread's UIDs, GIDs, supplementary /// groups, and capability sets are in an unknown and possibly inconsistent state. This is EXTREMELY /// DANGEROUS! If you are unable to revert the changes, abort as soon as possible. pub fn cap_set_ids( uid: Option, gid: Option, groups: Option<&[libc::gid_t]>, ) -> crate::Result<()> { let mut capstate = CapState::get_current()?; let orig_effective = capstate.effective; let orig_keepcaps = crate::prctl::get_keepcaps()?; crate::prctl::set_keepcaps(true)?; if gid.is_some() || groups.is_some() { capstate.effective.add(Cap::SETGID); } if uid.is_some() { capstate.effective.add(Cap::SETUID); } if capstate.effective != orig_effective { if let Err(err) = capstate.set_current() { crate::prctl::set_keepcaps(orig_keepcaps)?; return Err(err); } } let res = do_set_ids(uid, gid, groups); // Now clear the effective capability set (if it wasn't already cleared) and restore the // "keepcaps" flag. capstate.effective.clear(); res.and(capstate.set_current()) .and(crate::prctl::set_keepcaps(orig_keepcaps)) } cfg_if::cfg_if! { if #[cfg(all( target_pointer_width = "32", any(target_arch = "arm", target_arch = "sparc", target_arch = "x86") ))] { #[inline] unsafe fn setresuid(ruid: libc::uid_t, euid: libc::uid_t, suid: libc::uid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETRESUID32, ruid, euid, suid))?; } else { if libc::syscall(libc::SYS_setresuid32, ruid, euid, suid) < 0 { return Err(crate::Error::last()); } } } Ok(()) } #[inline] unsafe fn setresgid(rgid: libc::gid_t, egid: libc::gid_t, sgid: libc::gid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETRESGID32, rgid, egid, sgid))?; } else { if libc::syscall(libc::SYS_setresgid32, rgid, egid, sgid) < 0 { return Err(crate::Error::last()); } } } Ok(()) } #[inline] unsafe fn setgroups(size: libc::size_t, list: *const libc::gid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETGROUPS32, size, list))?; } else { if libc::syscall(libc::SYS_setgroups32, size, list) < 0 { return Err(crate::Error::last()); } } } Ok(()) } } else { #[inline] unsafe fn setresuid(ruid: libc::uid_t, euid: libc::uid_t, suid: libc::uid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETRESUID, ruid, euid, suid))?; } else { if libc::syscall(libc::SYS_setresuid, ruid, euid, suid) < 0 { return Err(crate::Error::last()); } } } Ok(()) } #[inline] unsafe fn setresgid(rgid: libc::gid_t, egid: libc::gid_t, sgid: libc::gid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETRESGID, rgid, egid, sgid))?; } else { if libc::syscall(libc::SYS_setresgid, rgid, egid, sgid) < 0 { return Err(crate::Error::last()); } } } Ok(()) } #[inline] unsafe fn setgroups(size: libc::size_t, list: *const libc::gid_t) -> crate::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { crate::sc_res_decode(sc::syscall!(SETGROUPS, size, list))?; } else { if libc::syscall(libc::SYS_setgroups, size, list) < 0 { return Err(crate::Error::last()); } } } Ok(()) } } } fn do_set_ids( uid: Option, gid: Option, groups: Option<&[libc::gid_t]>, ) -> crate::Result<()> { unsafe { if let Some(gid) = gid { setresgid(gid, gid, gid)?; } if let Some(groups) = groups { setgroups(groups.len(), groups.as_ptr())?; } if let Some(uid) = uid { setresuid(uid, uid, uid)?; } } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_set_ids_none() { // All this does is clear the effective capability set cap_set_ids(None, None, None).unwrap(); assert!(crate::caps::CapState::get_current() .unwrap() .effective .is_empty()); } #[test] fn test_set_ids_some() { let permitted_caps = crate::caps::CapState::get_current().unwrap().permitted; let uid = unsafe { libc::geteuid() }; let gid = unsafe { libc::getegid() }; if permitted_caps.has(crate::caps::Cap::SETUID) && permitted_caps.has(crate::caps::Cap::SETGID) { cap_set_ids(Some(uid), Some(gid), None).unwrap(); } else { assert_eq!( cap_set_ids(Some(uid), Some(gid), None).unwrap_err().code(), libc::EPERM ); } } } capctl-0.2.4/src/caps/mod.rs000064400000000000000000000302611046102023000137410ustar 00000000000000//! Interfaces to Linux capabilities. use core::fmt; mod cap_text; mod capset; mod capstate; mod helpers; #[cfg(feature = "serde")] mod serde_impl; #[cfg(feature = "std")] mod file; #[cfg(feature = "std")] pub use file::{FileCaps, ParseFileCapsError}; #[cfg(feature = "std")] mod fullcapstate; #[cfg(feature = "std")] pub use fullcapstate::FullCapState; pub mod ambient; pub mod bounding; pub use capset::{CapSet, CapSetIterator}; pub use capstate::{CapState, ParseCapStateError}; pub use helpers::cap_set_ids; /// Given a series of "paths" (i.e. `a::b`), yield the last one. macro_rules! last_path { ($last:path $(,)?) => { $last }; ($first:path$(, $rest:path)+ $(,)?) => { last_path! { $($rest),+ } } } macro_rules! define_cap { ($($name:ident = $val:literal,)+) => { /// An enum representing all of the possible Linux capabilities. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] #[repr(u8)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[non_exhaustive] pub enum Cap { $($name = $val,)+ } impl Cap { #[inline] fn from_u8(val: u8) -> Option { match val { $($val => Some(Self::$name),)* _ => None, } } } // WARNING: Unsafe code trusts these constants to be correct! const LAST_CAP: Cap = last_path!($(Cap::$name,)+); // Some other useful values derived from LAST_CAP const CAP_MAX: u8 = LAST_CAP as u8; const NUM_CAPS: u8 = CAP_MAX + 1; // Get the lower bits filled with ones const CAP_BITMASK: u64 = u64::MAX >> (63 - CAP_MAX); static CAP_NAMES: [&str; NUM_CAPS as usize] = [$(stringify!($name),)+]; }; } define_cap! { CHOWN = 0, DAC_OVERRIDE = 1, DAC_READ_SEARCH = 2, FOWNER = 3, FSETID = 4, KILL = 5, SETGID = 6, SETUID = 7, SETPCAP = 8, LINUX_IMMUTABLE = 9, NET_BIND_SERVICE = 10, NET_BROADCAST = 11, NET_ADMIN = 12, NET_RAW = 13, IPC_LOCK = 14, IPC_OWNER = 15, SYS_MODULE = 16, SYS_RAWIO = 17, SYS_CHROOT = 18, SYS_PTRACE = 19, SYS_PACCT = 20, SYS_ADMIN = 21, SYS_BOOT = 22, SYS_NICE = 23, SYS_RESOURCE = 24, SYS_TIME = 25, SYS_TTY_CONFIG = 26, MKNOD = 27, LEASE = 28, AUDIT_WRITE = 29, AUDIT_CONTROL = 30, SETFCAP = 31, MAC_OVERRIDE = 32, MAC_ADMIN = 33, SYSLOG = 34, WAKE_ALARM = 35, BLOCK_SUSPEND = 36, AUDIT_READ = 37, PERFMON = 38, BPF = 39, CHECKPOINT_RESTORE = 40, // Adding a new capability here is sufficient to make the library aware of it (though the // capability numbers MUST be consecutive) } impl Cap { /// Return an iterator over all of the capabilities enumerated by `Cap`. #[inline] pub fn iter() -> CapIter { CapIter { i: 0 } } #[inline] fn to_single_bitfield(self) -> u64 { // Sanity check to help ensure CAP_MAX is set correctly (note that this will only catch some // cases) debug_assert!((self as u8) <= CAP_MAX); 1u64 << (self as u8) } /// Checks whether the specified capability is supported on the current kernel. #[inline] pub fn is_supported(self) -> bool { bounding::read(self).is_some() } /// Determines the set of capabilities supported by the running kernel. /// /// This uses a binary search combined with [`Cap::is_supported()`] to determine the supported /// capabilities. It is more efficient than a simple `Cap::iter()`/`Cap::is_supported()` loop. /// /// [`Cap::is_supported()`]: #method.is_supported pub fn probe_supported() -> CapSet { // Do a binary search // Rust currently supports kernel 2.6.32+. CAP_MAC_ADMIN was the last capability added // before that release (in kernel 2.6.25). let mut min = Self::MAC_ADMIN as u8; let mut max = CAP_MAX; debug_assert!(Self::from_u8(min).unwrap().is_supported()); while min != max { // This basically does `mid = ceil((min + max) / 2)`. // If we don't do ceiling division, the way binary search works, we'll get stuck at // `max = min + 1` forever. let mid = (min + max + 1) >> 1; if Self::from_u8(mid).unwrap().is_supported() { min = mid; } else { max = mid - 1; } debug_assert!(max >= min); } CapSet::from_bitmask_truncate(u64::MAX >> (63 - min)) } pub(crate) fn name(self) -> &'static str { CAP_NAMES[self as usize] } } impl core::str::FromStr for Cap { type Err = ParseCapError; fn from_str(s: &str) -> Result { if s.len() > 4 && s[..4].eq_ignore_ascii_case("CAP_") { let s = &s[4..]; for (i, cap_name) in CAP_NAMES.iter().enumerate() { if cap_name.eq_ignore_ascii_case(s) { return Ok(Cap::from_u8(i as u8).unwrap()); } } } Err(ParseCapError(())) } } impl fmt::Display for Cap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("CAP_")?; fmt::Debug::fmt(self, f) } } /// Represents an error when parsing a `Cap` from a string. #[derive(Clone, Eq, PartialEq)] pub struct ParseCapError(()); impl fmt::Debug for ParseCapError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("ParseCapError") .field("message", &"Unknown capability") .finish() } } impl fmt::Display for ParseCapError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Unknown capability") } } #[cfg(feature = "std")] impl std::error::Error for ParseCapError {} /// An iterator over all the capabilities enumerated in `Cap`. /// /// This is constructed by [`Cap::iter()`]. /// /// [`Cap::iter()`]: ./enum.Cap.html#method.iter #[derive(Clone)] pub struct CapIter { i: u8, } impl Iterator for CapIter { type Item = Cap; fn next(&mut self) -> Option { debug_assert!(self.i <= NUM_CAPS); let cap = Cap::from_u8(self.i)?; self.i += 1; Some(cap) } fn nth(&mut self, n: usize) -> Option { if n < self.len() { self.i += n as u8; self.next() } else { // The specified index would exhaust the iterator self.i = NUM_CAPS; None } } #[inline] fn last(self) -> Option { if self.i < NUM_CAPS { Some(LAST_CAP) } else { None } } #[inline] fn count(self) -> usize { self.len() } #[inline] fn size_hint(&self) -> (usize, Option) { let len = self.len(); (len, Some(len)) } } impl ExactSizeIterator for CapIter { #[inline] fn len(&self) -> usize { (NUM_CAPS - self.i) as usize } } impl core::iter::FusedIterator for CapIter {} #[cfg(test)] mod tests { use core::str::FromStr; use super::*; #[allow(clippy::eq_op)] #[test] fn test_last_path() { // Briefly test the last_path! macro since unsafe code relies on it to be correct assert_eq!(last_path!(Cap::CHOWN), Cap::CHOWN); assert_eq!(last_path!(Cap::CHOWN, Cap::SETUID), Cap::SETUID); assert_eq!( last_path!(Cap::CHOWN, CAP::SETUID, Cap::SETGID,), Cap::SETGID ); } #[test] fn test_cap_u8() { for i in 0..NUM_CAPS { assert_eq!(Cap::from_u8(i).unwrap() as u8, i); } for i in NUM_CAPS..=u8::MAX { assert_eq!(Cap::from_u8(i), None); } } #[test] fn test_cap_string() { assert_eq!(Cap::from_str("CAP_CHOWN"), Ok(Cap::CHOWN)); assert_eq!(Cap::from_str("cap_CHOWN"), Ok(Cap::CHOWN)); assert_eq!(Cap::from_str("Cap_CHOWN"), Ok(Cap::CHOWN)); assert_eq!(Cap::from_str("CAP_sys_chroot"), Ok(Cap::SYS_CHROOT)); assert_eq!(Cap::from_str("cap_sys_chroot"), Ok(Cap::SYS_CHROOT)); assert_eq!(Cap::from_str("Cap_Sys_chroot"), Ok(Cap::SYS_CHROOT)); assert!(Cap::from_str("").is_err()); assert!(Cap::from_str("CAP_").is_err()); assert!(Cap::from_str("CHOWN").is_err()); assert!(Cap::from_str("CAP_NOEXIST").is_err()); #[cfg(feature = "std")] assert_eq!(Cap::CHOWN.to_string(), "CAP_CHOWN"); #[cfg(feature = "std")] for cap in Cap::iter() { let s = cap.to_string(); assert_eq!(Cap::from_str(&s), Ok(cap)); assert_eq!(Cap::from_str(&s.to_lowercase()), Ok(cap)); assert_eq!(Cap::from_str(&s.to_uppercase()), Ok(cap)); } for (cap, name) in Cap::iter().zip(&CAP_NAMES) { // Concatenate strings without allocating let mut full_name = [0u8; 30]; full_name[..4].copy_from_slice(b"cap_"); full_name[4..name.len() + 4].copy_from_slice(name.as_bytes()); assert_eq!( Cap::from_str(core::str::from_utf8(&full_name[..name.len() + 4]).unwrap()), Ok(cap) ); } } #[cfg(feature = "std")] #[allow(deprecated)] #[test] fn test_cap_string_error() { let err = ParseCapError(()); // Make sure clone() and eq() work // This will probably be optimized away because it's zero-sized, but it checks that the // struct derives Clone and Eq. assert_eq!(err, err.clone()); // Make sure the string representations match assert_eq!(err.to_string(), "Unknown capability"); assert_eq!( format!("{:?}", err), "ParseCapError { message: \"Unknown capability\" }" ); } #[test] fn test_cap_iter_last() { assert_eq!(Cap::iter().last(), Some(LAST_CAP)); let mut last = None; for cap in Cap::iter() { last = Some(cap); } assert_eq!(last, Some(LAST_CAP)); let mut it = Cap::iter(); for _ in it.by_ref() {} assert_eq!(it.len(), 0); assert_eq!(it.last(), None); } #[allow(clippy::iter_nth_zero)] #[test] fn test_cap_iter_nth() { let mut it = Cap::iter(); while let Some(cap) = it.clone().next() { assert_eq!(cap, it.nth(0).unwrap()); } assert_eq!(it.nth(0), None); assert_eq!(Cap::iter().nth(0), Some(Cap::CHOWN)); assert_eq!(Cap::iter().nth(1), Some(Cap::DAC_OVERRIDE)); assert_eq!(Cap::iter().nth(NUM_CAPS as usize - 1), Some(LAST_CAP)); } #[allow(clippy::iter_nth_zero)] #[test] fn test_cap_iter_fused() { let mut it = Cap::iter(); for _ in it.by_ref() {} for _ in 0..256 { assert_eq!(it.next(), None); assert_eq!(it.nth(0), None); } } #[test] fn test_cap_iter_count() { let mut it = Cap::iter(); let mut count = it.len(); assert_eq!(it.clone().count(), count); assert_eq!(it.size_hint(), (count, Some(count))); while let Some(_cap) = it.next() { count -= 1; assert_eq!(it.len(), count); assert_eq!(it.clone().count(), count); assert_eq!(it.size_hint(), (count, Some(count))); } assert_eq!(count, 0); assert_eq!(it.len(), 0); assert_eq!(it.clone().count(), 0); assert_eq!(it.size_hint(), (0, Some(0))); } #[test] fn test_cap_bits() { let mut mask: u64 = 0; for cap in Cap::iter() { let cap_bits = cap.to_single_bitfield(); assert_eq!(2u64.pow(cap as u32), cap_bits); mask |= cap_bits; } assert_eq!(mask, CAP_BITMASK); } #[test] fn test_supported_caps() { let supported_caps = Cap::probe_supported(); // Check that the binary search worked properly for cap in Cap::iter() { assert_eq!(supported_caps.has(cap), cap.is_supported(), "{:?}", cap); } } } capctl-0.2.4/src/caps/serde_impl.rs000064400000000000000000000045621046102023000153120ustar 00000000000000use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; use serde::ser::{Serialize, SerializeSeq, Serializer}; use super::CapSet; impl Serialize for CapSet { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut seq = serializer.serialize_seq(Some(self.size()))?; for element in self.iter() { seq.serialize_element(&element)?; } seq.end() } } struct CapSetVisitor; impl<'de> Visitor<'de> for CapSetVisitor { type Value = CapSet; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { write!(formatter, "a set of Linux capability names") } fn visit_seq>(self, mut seq: A) -> Result { let mut set = CapSet::empty(); while let Some(cap) = seq.next_element()? { set.add(cap); } Ok(set) } } impl<'de> Deserialize<'de> for CapSet { fn deserialize(deserializer: S) -> Result where S: Deserializer<'de>, { deserializer.deserialize_seq(CapSetVisitor) } } #[cfg(test)] mod tests { use super::*; use super::super::Cap; use serde_test::{assert_de_tokens_error, assert_tokens, Token}; #[test] fn test_ser_de_capset() { assert_tokens( &CapSet::empty(), &[Token::Seq { len: Some(0) }, Token::SeqEnd], ); assert_tokens( &crate::capset!(Cap::CHOWN), &[ Token::Seq { len: Some(1) }, Token::UnitVariant { name: "Cap", variant: "CHOWN", }, Token::SeqEnd, ], ); assert_tokens( &crate::capset!(Cap::CHOWN, Cap::FOWNER), &[ Token::Seq { len: Some(2) }, Token::UnitVariant { name: "Cap", variant: "CHOWN", }, Token::UnitVariant { name: "Cap", variant: "FOWNER", }, Token::SeqEnd, ], ); assert_de_tokens_error::( &[Token::String("CHOWN")], "invalid type: string \"CHOWN\", expected a set of Linux capability names", ); } } capctl-0.2.4/src/err.rs000064400000000000000000000101451046102023000130230ustar 00000000000000use core::fmt; pub type Result = core::result::Result; /// Represents an OS error encountered when performing an operation. /// /// Note: Parsing errors (i.e. errors returned by `FromStr` implementations) have their own types; /// for example [`ParseCapError`]). /// /// [`ParseCapError`]: ./caps/struct.ParseCapError.html pub struct Error(i32); impl Error { /// Get the last OS error that occured (i.e. the current `errno` value). #[inline] pub fn last() -> Self { Self(unsafe { *libc::__errno_location() }) } /// Construct an `Error` from an `errno` code. #[inline] pub fn from_code(eno: i32) -> Self { Self(eno) } /// Get the `errno` code represented by this `Error` object. #[inline] pub fn code(&self) -> i32 { self.0 } fn strerror<'a>(&self, buf: &'a mut [u8]) -> &'a str { static UNKNOWN_ERROR: &str = "Unknown error"; if self.0 < 0 { return UNKNOWN_ERROR; } let ret = unsafe { libc::strerror_r(self.0, buf.as_mut_ptr() as *mut _, buf.len()) }; if ret == libc::EINVAL { return UNKNOWN_ERROR; } assert_eq!(ret, 0, "strerror_r() returned {}", ret); #[cfg(feature = "std")] let msg = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const _) } .to_str() .unwrap(); #[cfg(not(feature = "std"))] let msg = { let len = buf.iter().position(|&ch| ch == 0).unwrap(); core::str::from_utf8(&buf[..len]).unwrap() }; #[cfg(target_env = "musl")] if msg == "No error information" { return UNKNOWN_ERROR; } msg } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut buf = [0u8; 1024]; f.write_str(self.strerror(&mut buf))?; write!(f, " (code {})", self.0) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut buf = [0u8; 1024]; let message = self.strerror(&mut buf); f.debug_struct("Error") .field("code", &self.0) .field("message", &message) .finish() } } #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] impl std::error::Error for Error {} #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] impl From for std::io::Error { #[inline] fn from(e: Error) -> Self { Self::from_raw_os_error(e.0) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_code() { assert_eq!(Error::from_code(libc::EPERM).code(), libc::EPERM); assert_eq!(Error::from_code(libc::ENOENT).code(), libc::ENOENT); } #[test] fn test_last() { unsafe { *libc::__errno_location() = libc::EPERM; } assert_eq!(Error::last().code(), libc::EPERM); unsafe { *libc::__errno_location() = libc::ENOENT; } assert_eq!(Error::last().code(), libc::ENOENT); } #[test] fn test_strerror() { let mut buf = [0u8; 1024]; assert_eq!( Error::from_code(libc::EISDIR).strerror(&mut buf), "Is a directory" ); assert_eq!(Error::from_code(-1).strerror(&mut buf), "Unknown error"); assert_eq!(Error::from_code(8192).strerror(&mut buf), "Unknown error"); } #[cfg(feature = "std")] #[test] fn test_display() { assert_eq!( Error::from_code(libc::EISDIR).to_string(), format!("Is a directory (code {})", libc::EISDIR) ); } #[cfg(feature = "std")] #[test] fn test_debug() { assert_eq!( format!("{:?}", Error::from_code(libc::EISDIR)), format!( "Error {{ code: {}, message: \"Is a directory\" }}", libc::EISDIR ) ); } #[cfg(feature = "std")] #[test] fn test_from_error() { assert_eq!( std::io::Error::from(Error::from_code(libc::ENOENT)).raw_os_error(), Some(libc::ENOENT) ); } } capctl-0.2.4/src/lib.rs000064400000000000000000000102101046102023000127720ustar 00000000000000//! # `capctl` //! //! A library for manipulating Linux capabilities and making `prctl()` calls. //! //! # Potential Pitfalls //! //! - See [Handling of newly-added capabilities](#handling-of-newly-added-capabilities). **This can //! create security issues if it is not accounted for.** //! //! ## Handling of capabilities not supported by the kernel //! //! When a binary using this library is running on an older kernel that does not support a few //! newly-added capabilities, here is how this library will handle them: //! //! - [`caps::Cap::is_supported()`] and [`caps::Cap::probe_supported()`] can be used to detect //! that the capability is unsupported (`cap.is_supported()` will return `false`, and //! `Cap::probe_supported()` will not include it in the returned set). //! - [`caps::CapState`] and [`caps::FullCapState`] will never include the unsupported capability(s) //! in the returned capability sets. //! - Trying to include the unsupported capability(s) in the new permitted/effective/inheritable //! sets with [`caps::CapState::set_current()`] will cause them to be silently removed from the //! new sets. (This is a kernel limitation.) //! - The following functions will return an `Error` with code `EINVAL` if passed the unsupported //! capability: //! - [`caps::bounding::drop()`] //! - [`caps::ambient::raise()`] //! - [`caps::ambient::lower()`] //! - [`caps::ambient::is_set()`] and [`caps::bounding::read()`] will return `None` if passed the //! unsupported capability. //! //! ## Handling of newly-added capabilities //! //! Conversely, when a binary using this library is running on a newer kernel that has added one or //! more new capabilities, issues can arise. Here is how this library will handle those //! capabilities: //! //! - If the permitted, effective, and/or inheritable capability sets of this process are modified //! (in any way) using [`caps::CapState`], the unknown capability(s) will be removed from the //! permitted, effective, and inheritable sets. //! - The following functions are the **ONLY** functions in this crate that can be used to remove //! the unknown capability(s) from the ambient/bounding sets (see their documentation for more //! information): //! - [`caps::ambient::clear()`] //! - [`caps::ambient::clear_unknown()`] //! - [`caps::bounding::clear()`] //! - [`caps::bounding::clear_unknown()`] //! //! As a result, if you are trying to clear the ambient and/or bounding capability sets, you must //! call the `clear()` or `clear_unknown()` function for whichever set you want to clear. #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] mod err; mod sys; pub mod caps; pub mod prctl; pub use caps::*; pub use err::*; pub use prctl::*; #[allow(clippy::needless_return)] #[inline] unsafe fn raw_prctl( option: libc::c_int, arg2: libc::c_ulong, arg3: libc::c_ulong, arg4: libc::c_ulong, arg5: libc::c_ulong, ) -> Result { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { return sc_res_decode(sc::syscall!(PRCTL, option, arg2, arg3, arg4, arg5)) .map(|res| res as libc::c_int); } else { let res = libc::prctl(option, arg2, arg3, arg4, arg5); return if res >= 0 { Ok(res) } else { Err(Error::last()) }; } } } #[inline] unsafe fn raw_prctl_opt( option: libc::c_int, arg2: libc::c_ulong, arg3: libc::c_ulong, arg4: libc::c_ulong, arg5: libc::c_ulong, ) -> Option { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { let res = sc::syscall!(PRCTL, option, arg2, arg3, arg4, arg5); if res <= -4096isize as usize { return Some(res as libc::c_int); } } else { let res = libc::prctl(option, arg2, arg3, arg4, arg5); if res >= 0 { return Some(res); } } } None } #[cfg(feature = "sc")] #[inline] fn sc_res_decode(res: usize) -> Result { if res > -4096isize as usize { Err(Error::from_code((!res + 1) as i32)) } else { Ok(res) } } capctl-0.2.4/src/prctl.rs000064400000000000000000001075561046102023000133740ustar 00000000000000//! Interfaces to `prctl()` commands that don't deal with capabilities. /// Set the name of the current thread. /// /// If the given name is longer than 15 bytes, it will be truncated to the first 15 bytes. /// /// (Note: Other documentation regarding Linux capabilities says that the maximum length is 16 /// bytes; that value includes the terminating NUL byte at the end of C strings.) #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] #[inline] pub fn set_name>(name: N) -> crate::Result<()> { use std::os::unix::ffi::OsStrExt; raw_set_name(name.as_ref().as_bytes()) } #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] fn raw_set_name(name: &[u8]) -> crate::Result<()> { if name.contains(&0) { return Err(crate::Error::from_code(libc::EINVAL)); } let mut buf = [0; 16]; let ptr = if name.len() < buf.len() { buf[..name.len()].copy_from_slice(name); buf.as_ptr() } else { // The kernel only looks at the first 16 bytes, so we can use the original string name.as_ptr() }; unsafe { crate::raw_prctl(libc::PR_SET_NAME, ptr as libc::c_ulong, 0, 0, 0)?; } Ok(()) } /// Get the name of the current thread. #[cfg_attr(docsrs, doc(cfg(feature = "std")))] #[cfg(feature = "std")] pub fn get_name() -> crate::Result { use std::os::unix::ffi::OsStringExt; let mut name_vec = vec![0; 16]; unsafe { crate::raw_prctl( libc::PR_GET_NAME, name_vec.as_ptr() as libc::c_ulong, 0, 0, 0, )?; } name_vec.truncate(name_vec.iter().position(|x| *x == 0).unwrap()); Ok(std::ffi::OsString::from_vec(name_vec)) } /// Get the no-new-privileges flag of the current thread. /// /// See [`set_no_new_privs()`](./fn.set_no_new_privs.html) for more details. #[inline] pub fn get_no_new_privs() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) }?; Ok(res != 0) } /// Enable the no-new-privileges flag on the current thread. /// /// If this flag is enabled, `execve()` will no longer honor set-user-ID/set-group-ID bits and file /// capabilities on executables. See prctl(2) for more details. /// /// Once this is enabled, it cannot be unset. #[inline] pub fn set_no_new_privs() -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)?; } Ok(()) } /// Get the "keep capabilities" flag of the current thread. /// /// See [`set_keepcaps()`](./fn.set_keepcaps.html) for more details. #[inline] pub fn get_keepcaps() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_GET_KEEPCAPS, 0, 0, 0, 0) }?; Ok(res != 0) } /// Set the "keep capabilities" flag of the current thread. /// /// Setting this flag allows a thread to retain its permitted capabilities when switching all its /// UIDs to non-zero values (the effective capability set is still emptied). /// /// This flag is always cleared on an `execve()`; see capabilities(7) for more details. #[inline] pub fn set_keepcaps(keep: bool) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_KEEPCAPS, keep as libc::c_ulong, 0, 0, 0)?; } Ok(()) } /// Get the "dumpable" flag for the current process. /// /// See [`set_dumpable()`](./fn.set_dumpable.html) for more details. #[inline] pub fn get_dumpable() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_GET_DUMPABLE, 0, 0, 0, 0) }?; Ok(res != 0) } /// Set the "dumpable" flag for the current process. /// /// This controls whether a core dump will be produced for the process if it receives a signal that /// would make it perform a core dump. It also restricts which processes can be attached with /// `ptrace()`. #[inline] pub fn set_dumpable(dumpable: bool) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_DUMPABLE, dumpable as libc::c_ulong, 0, 0, 0)?; } Ok(()) } /// Set the "child subreaper" flag for the current process. /// /// If a process dies, its children will be reparented to the nearest surviving ancestor subreaper, /// or to PID 1 if it has no ancestor subreapers. /// /// This is useful for process managers that need to be informed when any of their descendants /// (possibly processes that used the double-`fork()` trick to become daemons) die. #[inline] pub fn set_subreaper(flag: bool) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_CHILD_SUBREAPER, flag as libc::c_ulong, 0, 0, 0)?; } Ok(()) } /// Get the "child subreaper" flag for the current process. /// /// See [`set_subreaper()`](./fn.set_subreaper.html) for more detailss. #[inline] pub fn get_subreaper() -> crate::Result { let mut res = 0; unsafe { crate::raw_prctl( libc::PR_GET_CHILD_SUBREAPER, (&mut res) as *mut libc::c_int as libc::c_ulong, 0, 0, 0, )?; } Ok(res != 0) } /// Set the parent-death signal of the current process. /// /// The parent-death signal is the signal that this process will receive when its parent dies. It /// is cleared when executing a binary that is set-UID, set-GID, or has file capabilities. /// /// Specifying `None` is equivalent to specifying `Some(0)`; both clear the parent-death signal. #[inline] pub fn set_pdeathsig(sig: Option) -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_SET_PDEATHSIG, sig.unwrap_or(0) as libc::c_ulong, 0, 0, 0, )?; } Ok(()) } /// Get the parent-death signal of the current process. /// /// This returns `Ok(None)` if the process's parent-death signal is cleared, and `Ok(Some(sig))` /// otherwise. #[inline] pub fn get_pdeathsig() -> crate::Result> { let mut sig = 0; unsafe { crate::raw_prctl( libc::PR_GET_PDEATHSIG, (&mut sig) as *mut libc::c_int as libc::c_ulong, 0, 0, 0, )?; } Ok(if sig == 0 { None } else { Some(sig) }) } bitflags::bitflags! { /// Represents the thread's securebits flags. pub struct Secbits: libc::c_ulong { /// If this flag is set, the kernel does not grant capabilities when a SUID-root program is /// executed, or when a process with an effective/real UID of 0 calls `exec()`. const NOROOT = 0x1; /// Locks the `NOROOT` flag so it cannot be changed. const NOROOT_LOCKED = 0x2; /// If this flag is set, the kernel will not adjust the current thread's /// permitted/effective/inheritable capability sets when its effective and filesystem UIDs /// are changed between zero and nonzero values. /// const NO_SETUID_FIXUP = 0x4; /// Locks the `NO_SETUID_FIXUP` flag so it cannot be changed. const NO_SETUID_FIXUP_LOCKED = 0x8; /// If this flag is set, the kernel will not empty the current thread's permitted /// capability set when all of its UIDs are switched to nonzero values. (However, the /// effective capability set will still be cleared.) /// /// This flag is cleared across `execve()` calls. /// /// Note: [`get_keepcaps()`] and [`set_keepcaps()`] provide the same functionality as this /// flag (setting the flag via [`set_keepcaps()`] will change its value as perceived by /// [`get_securebits()`], and vice versa). However, [`set_keepcaps()`] does not require /// CAP_SETPCAP; changing the securebits does. As a result, if you only need to manipulate /// the `KEEP_CAPS` flag, you may wish to instead use [`get_keepcaps()`] and /// [`set_keepcaps()`]. /// /// [`get_keepcaps()`]: ./fn.get_keepcaps.html /// [`set_keepcaps()`]: ./fn.set_keepcaps.html const KEEP_CAPS = 0x10; /// Locks the `KEEP_CAPS` flag so it cannot be changed. /// /// Note: The `KEEP_CAPS` flag is always cleared across `execve()`, even if it is "locked" /// using this flag. As a result, this flag is mainly useful for locking the `KEEP_CAPS` /// flag in the "off" setting. const KEEP_CAPS_LOCKED = 0x20; /// Disallows raising ambient capabilities. const NO_CAP_AMBIENT_RAISE = 0x40; /// Locks the `NO_CAP_AMBIENT_RAISE_LOCKED` flag so it cannot be changed. const NO_CAP_AMBIENT_RAISE_LOCKED = 0x80; } } /// Get the "securebits" flags of the current thread. /// /// See [`set_securebits()`](./fn.set_securebits.html) for more details. #[inline] pub fn get_securebits() -> crate::Result { let f = unsafe { crate::raw_prctl(libc::PR_GET_SECUREBITS, 0, 0, 0, 0) }?; Ok(Secbits::from_bits_truncate(f as libc::c_ulong)) } /// Set the "securebits" flags of the current thread. /// /// The secure bits control various aspects of the handling of capabilities for UID 0. See /// [`Secbits`](struct.Secbits.html) and capabilities(7) for more details. /// /// Note: Modifying the securebits with this function requires the CAP_SETPCAP capability. #[inline] pub fn set_securebits(flags: Secbits) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_SECUREBITS, flags.bits(), 0, 0, 0)?; } Ok(()) } /// Get the secure computing mode of the current thread. /// /// If the thread is not in secure computing mode, this function returns `false`; if it is in /// seccomp filter mode (and the `prctl()` syscall with the given arguments is allowed by the /// filters) then this function returns `true`; if it is in strict computing mode then it will be /// sent a SIGKILL signal. #[inline] pub fn get_seccomp() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_GET_SECCOMP, 0, 0, 0, 0) }?; Ok(res != 0) } /// Enable strict secure computing mode. /// /// After this call, any syscalls except `read()`, `write()`, `_exit()`, and `sigreturn()` will /// cause the thread to be terminated with SIGKILL. #[inline] pub fn set_seccomp_strict() -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_SET_SECCOMP, libc::SECCOMP_MODE_STRICT as libc::c_ulong, 0, 0, 0, )?; } Ok(()) } /// Get the current timer slack value. /// /// See [`set_timerslack()`](./fn.set_timerslack.html) for more details. /// /// # Behavior at extreme values /// /// This function may not work correctly (specifically, it may return strange `Err` values) if the /// current timer slack value is larger than `libc::c_ulong::MAX - 4095` or so. Unfortunately, this /// isn't really possible to fix because of the design of the underlying `prctl()` call. However, /// most users are unlikely to encounter this error because timer slack values in this range are /// generally not useful. /// /// If you *really* need to handle values in this range, try /// `std::fs::read_to_string("/proc/self/timerslack_ns")?.trim().parse::().unwrap()` /// (only works on Linux 4.6+). #[allow(clippy::needless_return)] #[inline] pub fn get_timerslack() -> crate::Result { cfg_if::cfg_if! { if #[cfg(feature = "sc")] { return crate::sc_res_decode(unsafe { sc::syscall!(PRCTL, libc::PR_GET_TIMERSLACK, 0, 0, 0) }).map(|res| res as libc::c_ulong); } else { let res = unsafe { libc::syscall(libc::SYS_prctl, libc::PR_GET_TIMERSLACK, 0, 0, 0) }; return if res == -1 { Err(crate::Error::last()) } else { Ok(res as libc::c_ulong) }; } } } /// Set the current timer slack value. /// /// The timer slack value is used by the kernel to group timer expirations (`select()`, /// `epoll_wait()`, `nanosleep()`, etc.) for the calling thread. See prctl(2) for more details. /// /// Note: Passing a value of 0 will reset the current timer slack value to the "default" timer /// slack value (which is inherited from the parent). Again, prctl(2) contains more information. #[inline] pub fn set_timerslack(new_slack: libc::c_ulong) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_TIMERSLACK, new_slack, 0, 0, 0)?; } Ok(()) } /// Set the status of the "THP" disable flag. /// /// This flag provides an easy way to disable transparent huge pages process-wide. See `prctl(2)` /// for more details. #[inline] pub fn set_thp_disable(disable: bool) -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_SET_THP_DISABLE, disable as _, 0, 0, 0)?; } Ok(()) } /// Get the status of the "THP" disable flag. /// /// See [`set_thp_disable()`] and `prctl(2)`. #[inline] pub fn get_thp_disable() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_GET_THP_DISABLE, 0, 0, 0, 0) }?; Ok(res != 0) } /// A value that can be passed to [`set_ptracer()`]. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum Ptracer { /// Clear the current process's "ptracer process ID". None, /// Disable the Yama LSM's `ptrace()` restrictions for the current process. Any, /// Allow the specified process to `ptrace()` the current process (in addition to the current /// process's direct ancestors). Pid(libc::pid_t), } /// Set the calling process's "ptracer process ID". /// /// Normally, if the Yama LSM is enabled and in "restricted ptrace" mode (i.e. /// `/proc/sys/kernel/yama/ptrace_scope` contains the value `1`), a process can only be `ptrace()`d /// by one of its direct ancestors. This function allows a process to indicate that another process /// can also `ptrace()` it. /// /// For more information, see [`Ptracer`] and `prctl(2)`. #[inline] pub fn set_ptracer(ptracer: Ptracer) -> crate::Result<()> { let pid = match ptracer { Ptracer::None => 0, Ptracer::Any => crate::sys::PR_SET_PTRACER_ANY, Ptracer::Pid(pid) if pid <= 0 => return Err(crate::Error::from_code(libc::EINVAL)), Ptracer::Pid(pid) => pid as libc::c_ulong, }; unsafe { crate::raw_prctl(libc::PR_SET_PTRACER, pid, 0, 0, 0)?; } Ok(()) } /// The possible memory corruption kill policies. /// /// See [`get_mce_kill()`] and [`set_mce_kill()`] for usage. /// /// For more detailed information, see `prctl(2)`. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] #[repr(i32)] pub enum MceKill { /// When irrecoverable corruption is detected, immediately kill this thread if it has the /// corrupted page mapped. Early = libc::PR_MCE_KILL_EARLY, /// When irrecoverable corruption is detected, kill this thread if it tries to access the /// corrupted page. Late = libc::PR_MCE_KILL_LATE, /// Follow the system-wide default action specified in `/proc/sys/vm/memory_failure_early_kill` /// if irrecoverable corruption is detected. Default = libc::PR_MCE_KILL_DEFAULT, } /// Set the current thread's memory corruption kill policy. /// /// See [`MceKill`] for the different policies that can be used. /// /// This policy is inherited by children. #[inline] pub fn set_mce_kill(policy: MceKill) -> crate::Result<()> { unsafe { crate::raw_prctl( libc::PR_MCE_KILL, libc::PR_MCE_KILL_SET as _, policy as _, 0, 0, )?; } Ok(()) } /// Get the current thread's memory corruption kill policy. /// /// See [`set_mce_kill()`] for more information. #[inline] pub fn get_mce_kill() -> crate::Result { let res = unsafe { crate::raw_prctl(libc::PR_MCE_KILL_GET, 0, 0, 0, 0) }?; Ok(match res { libc::PR_MCE_KILL_EARLY => MceKill::Early, libc::PR_MCE_KILL_LATE => MceKill::Late, libc::PR_MCE_KILL_DEFAULT => MceKill::Default, _ => unreachable!(), }) } /// The different types of speculation misfeatures that can be passed to [`get_speculation_ctrl()`] /// and [`set_speculation_ctrl()`]. /// /// For information see `prctl(2)` or the kernel documentation. #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] #[repr(i32)] pub enum SpecVariant { /// The speculation store bypass misfeature. Added in Linux 4.17; only present on x86, x86_64, /// and PowerPC. StoreBypass = crate::sys::PR_SPEC_STORE_BYPASS, /// The indirect branch speculation misfeature. Added in Linux 4.20; only present on x86 and /// x86_64. IndirectBranch = crate::sys::PR_SPEC_INDIRECT_BRANCH, /// If this feature is enabled, flush the L1 data cache on context switches out of the current /// task. Does not address a specific vulnerability, but guards against future attacks. /// /// Added in Linux 5.15; only present on x86 and x86_64. /// /// If the CPU does not have support for flushing the L1D cache, [`get_speculation_ctrl()`] /// will return [`SpecFlags::FORCE_DISABLE`], and [`set_speculation_ctrl()`] will fail with /// `EPERM`. /// /// Note: This feature behaves unlike other `SpecVariant`s. Namely, passing /// [`SpecFlags::ENABLE`] **enables mitigation** instead of enabling the misfeature. L1DFlush = crate::sys::PR_SPEC_L1D_FLUSH, } bitflags::bitflags! { /// Flags indicating the state of the "speculation misfeature". See [`get_speculation_ctrl()`] /// and [`set_speculation_ctrl()`]. pub struct SpecFlags: libc::c_int { /// Indicates that mitigation for the specified variant can be controlled using /// [`set_speculation_ctrl()`]. /// /// This flag may be set in the return value of [`get_speculation_ctrl()`]; it should not /// be passed to [`set_speculation_ctrl()`]. const PRCTL = crate::sys::PR_SPEC_PRCTL; /// Speculation attacks are enabled; mitigation is disabled. /// /// (Note that this is reversed for [`SpecVariant::L1DFlush`]) const ENABLE = crate::sys::PR_SPEC_ENABLE; /// Speculation attacks are disabled; mitigation is enabled. /// /// (Note that this is reversed for [`SpecVariant::L1DFlush`]) const DISABLE = crate::sys::PR_SPEC_DISABLE; /// Same as [`Self::DISABLE`], but "locks" the value so it cannot be undone later. /// /// This flag is only supported for some of the variants. If it is passed to /// [`set_speculation_ctrl()`] for a variant that does not support it, the call will fail /// with `ERANGE`. const FORCE_DISABLE = crate::sys::PR_SPEC_FORCE_DISABLE; /// Same as [`Self::DISABLE`], but will be cleared on an `execve()`. /// /// This flag is only supported since Linux 5.1, and only for some of the variants. If it /// is passed to [`set_speculation_ctrl()`] for a variant that does not support it, the /// call will fail with `ERANGE`. const DISABLE_NOEXEC = crate::sys::PR_SPEC_DISABLE_NOEXEC; } } /// Get the current state of the "speculation misfeature". /// /// `variant` indicates the speculation "misfeature" whose status should be queried. If this /// function returns `EINVAL` or `ENODEV`, then either the kernel is unaware of the misfeature or /// the CPU architecture is not affected by this type of misfeature. /// /// This function returns "flags" which indicate the current status. If the flags are empty (i.e. /// `.is_empty()` returns true) then the CPU is not affected by this misfeature. Otherwise, the /// returned flags indicate the current state and whether it can be controlled by the user. See /// [`SpecFlags`] for more information. #[inline] pub fn get_speculation_ctrl(variant: SpecVariant) -> crate::Result { let res = unsafe { crate::raw_prctl(crate::sys::PR_GET_SPECULATION_CTRL, variant as _, 0, 0, 0) }?; Ok(SpecFlags::from_bits_truncate(res)) } /// Set the per-thread state of the "speculation misfeature". /// /// `variant` indicates the speculation "misfeature" whose status should be modified. If this /// function returns `EINVAL` or `ENODEV`, then either the kernel is unaware of the misfeature or /// the CPU architecture is not affected by this type of misfeature. /// /// `control` indicates how to modify the mitigation. Any single one of the [`SpecFlags`] (except /// for [`SpecFlags::PRCTL`]) can be passed. However, they should NOT be ORed together; this will /// cause the call to fail with `ERANGE`. /// /// This function may fail with `EINVAL` or `ENODEV` if the kernel is unaware of the misfeature or /// the CPU architecture is not affected by this type of misfeature, or with `EPERM` or `ENXIO` if /// the status cannot be modified (e.g. mitigation was forcibly disabled at boot). #[inline] pub fn set_speculation_ctrl(variant: SpecVariant, control: SpecFlags) -> crate::Result<()> { unsafe { crate::raw_prctl( crate::sys::PR_SET_SPECULATION_CTRL, variant as _, control.bits() as _, 0, 0, )?; } Ok(()) } /// Get this thread's `clear_child_tid` address. /// /// See `prctl(2)`, `set_tid_address(2)`, and `clone(2)` for more information. /// /// Note: This function accounts for the 32-bit issues on x86 and MIPS that `prctl(2)` warns about. #[inline] pub fn get_tid_address() -> crate::Result<*mut libc::c_int> { cfg_if::cfg_if! { if #[cfg(any(target_arch = "x86", target_arch = "mips"))] { // On the 64-bit versions of these arches, there is no 32-bit compatibility code, so the // kernel writes out a 64-bit pointer. We need to handle this properly. let mut buf = 0u64; unsafe { crate::raw_prctl(libc::PR_GET_TID_ADDRESS, &mut buf as *mut _ as _, 0, 0, 0)?; } let addr = if cfg!(target_endian = "big") { // We have 2 possible cases: // // On real 32-bit systems (kernel writes 32-bit pointers): // 0xDEADBEEF_00000000 // ^^^^^^^^ ^^^^^^^^ // | | // | zeroes from when we initialize `buf` // | // address (since the kernel only writes the first 32 bits) // // On 64-bit systems (kernel writes 64-bit pointers): // 0x00000000_DEADBEEF // ^^^^^^^^ ^^^^^^^^ // | | // | lower 32 bits (the real address) // | // upper 32 bits of the address (should be zero since we're a 32-bit process) if buf & 0xFFFF_FFFF == 0 { // If the lower 32 bits are 0, take the upper 32 bits (buf >> 32) as *mut libc::c_int } else { // If the lower 32 bits are nonzero, take them debug_assert_eq!(buf >> 32, 0, "address too large"); buf as *mut libc::c_int } } else { // We have 2 possible cases: // // On real 32-bit systems (kernel writes 32-bit pointers): // 0xFEEBDAED_00000000 // ^^^^^^^^ ^^^^^^^^ // | | // | zeroes from when we initialize `buf` // | // address (since the kernel only writes the first 32 bits) // // On 64-bit systems (kernel writes 64-bit pointers): // 0xFEEBDAED_00000000 // ^^^^^^^^ ^^^^^^^^ // | | // | upper 32 bits of the address (should be zero since we're 32-bit) // | // lower 32 bits (the real address) // // In both cases, we can just cast it to a 32-bit pointer debug_assert_eq!(buf >> 32, 0, "address too large"); buf as *mut libc::c_int }; } else { let mut addr = core::ptr::null_mut(); unsafe { crate::raw_prctl(libc::PR_GET_TID_ADDRESS, &mut addr as *mut _ as _, 0, 0, 0)?; } } } Ok(addr) } /// Enable all performance counters attached to the current process. /// /// This is the opposite of [`disable_perf_events()]. #[inline] pub fn enable_perf_events() -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_TASK_PERF_EVENTS_ENABLE, 0, 0, 0, 0)?; } Ok(()) } /// Disable all performance counters attached to the current process. /// /// Performance counters are created using perf_event_open(2) and can be used to measure performance /// information. /// /// See prctl(2) for more information. #[inline] pub fn disable_perf_events() -> crate::Result<()> { unsafe { crate::raw_prctl(libc::PR_TASK_PERF_EVENTS_DISABLE, 0, 0, 0, 0)?; } Ok(()) } /// Get the "I/O flusher" flag for the current process. /// /// See [`set_io_flusher()`] for more details. #[inline] pub fn get_io_flusher() -> crate::Result { let res = unsafe { crate::raw_prctl(crate::sys::PR_GET_IO_FLUSHER, 0, 0, 0, 0) }?; Ok(res != 0) } /// Set the "I/O flusher" flag for the current process. (Linux 5.6+) /// /// User processes which are involved in filesystem I/O (e.g. FUSE daemons) and which may allocate /// memory while handling requests should set this flag to `true`. This gives the process special /// treatment when it tries to allocate memory; see prctl(2) for details. /// /// Changing the I/O flusher status requires the `CAP_SYS_RESOURCE` capability. #[inline] pub fn set_io_flusher(flusher: bool) -> crate::Result<()> { unsafe { crate::raw_prctl( crate::sys::PR_SET_IO_FLUSHER, flusher as libc::c_ulong, 0, 0, 0, )?; } Ok(()) } bitflags::bitflags! { /// Flags controlling memory-deny-write-execute behavior. /// /// Can be used with [`set_mdwe()`] and [`get_mdwe()`]. pub struct MDWEFlags: libc::c_int { /// Disallow creating a mapping which is executable and was at some point writeable. /// /// This blocks not only `mmap(PROT_READ | PROT_WRITE | PROT_EXEC)`, but e.g. /// `mmap(PROT_READ | PROT_WRITE); mprotect(PROT_EXEC)`. /// /// This flag cannot be unset once it is set. const REFUSE_EXEC_GAIN = crate::sys::PR_MDWE_REFUSE_EXEC_GAIN; } } /// Set the memory-deny-write execute flags. /// /// Currently there is only one flag ([`MDWEFlags::REFUSE_EXEC_GAIN`]), which disallows creating /// executable mappings that are/were writable. #[inline] pub fn set_mdwe(flags: MDWEFlags) -> crate::Result<()> { unsafe { crate::raw_prctl(crate::sys::PR_SET_MDWE, flags.bits() as _, 0, 0, 0) }?; Ok(()) } /// Get the memory-deny-write execute flags. /// /// See [`set_mdwe()`] for more details. #[inline] pub fn get_mdwe() -> crate::Result { let res = unsafe { crate::raw_prctl(crate::sys::PR_GET_MDWE, 0, 0, 0, 0) }?; Ok(MDWEFlags::from_bits_truncate(res)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_keepcaps() { let old_keepcaps = get_keepcaps().unwrap(); set_keepcaps(true).unwrap(); assert!(get_keepcaps().unwrap()); assert!(get_securebits().unwrap().contains(Secbits::KEEP_CAPS)); set_keepcaps(false).unwrap(); assert!(!get_keepcaps().unwrap()); assert!(!get_securebits().unwrap().contains(Secbits::KEEP_CAPS)); set_keepcaps(old_keepcaps).unwrap(); } #[test] fn test_nnp() { set_no_new_privs().unwrap(); assert!(get_no_new_privs().unwrap()); set_no_new_privs().unwrap(); assert!(get_no_new_privs().unwrap()); } #[test] fn test_subreaper() { let was_subreaper = get_subreaper().unwrap(); set_subreaper(false).unwrap(); assert!(!get_subreaper().unwrap()); set_subreaper(true).unwrap(); assert!(get_subreaper().unwrap()); set_subreaper(was_subreaper).unwrap(); } #[test] fn test_pdeathsig() { let orig_pdeathsig = get_pdeathsig().unwrap(); set_pdeathsig(None).unwrap(); assert_eq!(get_pdeathsig().unwrap(), None); set_pdeathsig(Some(0)).unwrap(); assert_eq!(get_pdeathsig().unwrap(), None); set_pdeathsig(Some(libc::SIGCHLD)).unwrap(); assert_eq!(get_pdeathsig().unwrap(), Some(libc::SIGCHLD)); assert_eq!(set_pdeathsig(Some(-1)).unwrap_err().code(), libc::EINVAL); set_pdeathsig(orig_pdeathsig).unwrap(); } #[test] fn test_dumpable() { assert!(get_dumpable().unwrap()); // We can't set it to false because somebody may be ptrace()ing us during testing set_dumpable(true).unwrap(); assert!(get_dumpable().unwrap()); } #[cfg(feature = "std")] #[test] fn test_name() { let orig_name = get_name().unwrap(); set_name("capctl-short").unwrap(); assert_eq!(get_name().unwrap(), "capctl-short"); set_name("capctl-very-very-long").unwrap(); assert_eq!(get_name().unwrap(), "capctl-very-ver"); assert_eq!(set_name("a\0").unwrap_err().code(), libc::EINVAL); set_name(&orig_name).unwrap(); assert_eq!(get_name().unwrap(), orig_name); } #[test] fn test_securebits() { if crate::caps::CapState::get_current() .unwrap() .effective .has(crate::caps::Cap::SETPCAP) { let orig_secbits = get_securebits().unwrap(); let mut secbits = orig_secbits; secbits.insert(Secbits::KEEP_CAPS); set_securebits(secbits).unwrap(); assert!(get_keepcaps().unwrap()); secbits.remove(Secbits::KEEP_CAPS); set_securebits(secbits).unwrap(); assert!(!get_keepcaps().unwrap()); set_securebits(orig_secbits).unwrap(); } else { assert_eq!( set_securebits(get_securebits().unwrap()) .unwrap_err() .code(), libc::EPERM ); } } #[test] fn test_get_seccomp() { // We might be running in a Docker container or something with seccomp rules, so we can't // check the return value get_seccomp().unwrap(); } #[test] fn test_set_seccomp_strict() { match unsafe { libc::fork() } { -1 => panic!("{}", crate::Error::last()), 0 => { set_seccomp_strict().unwrap(); unsafe { libc::syscall(libc::SYS_exit, 0); libc::_exit(1); } } pid => { let mut wstatus = 0; if unsafe { libc::waitpid(pid, &mut wstatus, 0) } != pid { panic!("{}", crate::Error::last()); } assert!(libc::WIFEXITED(wstatus)); assert_eq!(libc::WEXITSTATUS(wstatus), 0); } } } #[cfg(feature = "std")] #[test] fn test_timerslack() { let orig_timerslack = get_timerslack().unwrap(); set_timerslack(orig_timerslack + 1).unwrap(); std::thread::spawn(move || { // The timer slack value is inherited assert_eq!(get_timerslack().unwrap(), orig_timerslack + 1); // We can change it set_timerslack(orig_timerslack).unwrap(); assert_eq!(get_timerslack().unwrap(), orig_timerslack); // And if we set it to "0", it reverts to the "default" value inherited from the parent // thread set_timerslack(0).unwrap(); assert_eq!(get_timerslack().unwrap(), orig_timerslack + 1); }) .join() .unwrap(); } #[test] fn test_thp_disable() { let orig_thp_disable = get_thp_disable().unwrap(); set_thp_disable(true).unwrap(); assert!(get_thp_disable().unwrap()); set_thp_disable(false).unwrap(); assert!(!get_thp_disable().unwrap()); set_thp_disable(orig_thp_disable).unwrap(); assert_eq!(get_thp_disable().unwrap(), orig_thp_disable); } #[cfg(feature = "std")] #[test] fn test_ptracer() { // Invalid value; disallowed by our wrapper assert_eq!( set_ptracer(Ptracer::Pid(0)).unwrap_err().code(), libc::EINVAL ); assert_eq!( set_ptracer(Ptracer::Pid(-1)).unwrap_err().code(), libc::EINVAL ); if std::path::Path::new("/proc/sys/kernel/yama/ptrace_scope").exists() { // The Yama LSM is enabled, and set_ptracer() will actually work // Nonexistent process; denied by the Yama LSM assert_eq!( set_ptracer(Ptracer::Pid(libc::pid_t::MAX)) .unwrap_err() .code(), libc::EINVAL ); // Setting it to a real process works set_ptracer(Ptracer::Pid(1)).unwrap(); set_ptracer(Ptracer::Pid(unsafe { libc::getppid() })).unwrap(); set_ptracer(Ptracer::Pid(unsafe { libc::getpid() })).unwrap(); // Clear it at the end set_ptracer(Ptracer::None).unwrap(); } else { // Every call fails with EINVAL for &pid in unsafe { [libc::pid_t::MAX, 1, libc::getppid(), libc::getpid()] }.iter() { assert_eq!( set_ptracer(Ptracer::Pid(pid)).unwrap_err().code(), libc::EINVAL ); } assert_eq!(set_ptracer(Ptracer::None).unwrap_err().code(), libc::EINVAL); } } #[test] fn test_mce_kill() { let orig_mce_kill = get_mce_kill().unwrap(); set_mce_kill(MceKill::Early).unwrap(); assert_eq!(get_mce_kill().unwrap(), MceKill::Early); set_mce_kill(MceKill::Late).unwrap(); assert_eq!(get_mce_kill().unwrap(), MceKill::Late); set_mce_kill(MceKill::Default).unwrap(); assert_eq!(get_mce_kill().unwrap(), MceKill::Default); set_mce_kill(orig_mce_kill).unwrap(); assert_eq!(get_mce_kill().unwrap(), orig_mce_kill); } #[test] fn test_get_tid_address() { // We don't know for sure how the clear_child_tid address is being used, so we can't check // it get_tid_address().unwrap(); } #[test] fn test_mdwe() { match get_mdwe() { Ok(orig_mdwe) => { if !orig_mdwe.contains(MDWEFlags::REFUSE_EXEC_GAIN) { set_mdwe(orig_mdwe | MDWEFlags::REFUSE_EXEC_GAIN).unwrap(); assert_eq!(get_mdwe().unwrap(), orig_mdwe | MDWEFlags::REFUSE_EXEC_GAIN); } assert_eq!( set_mdwe(MDWEFlags::empty()).unwrap_err().code(), libc::EPERM ); unsafe { // mmap(PROT_READ | PROT_WRITE | PROT_EXEC) fails let ptr = libc::mmap( core::ptr::null_mut(), 1, libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, -1, 0, ); assert_eq!(ptr, libc::MAP_FAILED); // mmap(PROT_READ | PROT_WRITE) followed by mprotect(PROT_EXEC) fails let ptr = libc::mmap( core::ptr::null_mut(), 1, libc::PROT_READ | libc::PROT_WRITE, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, -1, 0, ); assert_ne!(ptr, libc::MAP_FAILED); let res = libc::mprotect(ptr, 1, libc::PROT_EXEC); assert_eq!(res, -1); assert_eq!(crate::Error::last().code(), libc::EACCES); libc::munmap(ptr, 1); } } // EINVAL -> kernel does not support PR_GET_MDWE Err(e) if e.code() == libc::EINVAL => (), Err(e) => panic!("{}", e), } } } capctl-0.2.4/src/sys.rs000064400000000000000000000041351046102023000130530ustar 00000000000000#[cfg(not(feature = "sc"))] extern "C" { pub fn capget(hdrp: *mut cap_user_header_t, datap: *mut cap_user_data_t) -> libc::c_int; pub fn capset(hdrp: *mut cap_user_header_t, datap: *const cap_user_data_t) -> libc::c_int; } #[repr(C)] pub struct cap_user_header_t { pub version: u32, pub pid: libc::c_int, } #[derive(Copy, Clone)] #[repr(C)] pub struct cap_user_data_t { pub effective: u32, pub permitted: u32, pub inheritable: u32, } // WARNING: Updating to newer versions may require significant // code changes to caps/capstate.rs pub const _LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522; pub const PR_SET_PTRACER_ANY: libc::c_ulong = -1i32 as libc::c_ulong; pub const PR_GET_SPECULATION_CTRL: libc::c_int = 52; pub const PR_SET_SPECULATION_CTRL: libc::c_int = 53; pub const PR_SPEC_STORE_BYPASS: libc::c_int = 0; pub const PR_SPEC_INDIRECT_BRANCH: libc::c_int = 1; pub const PR_SPEC_L1D_FLUSH: libc::c_int = 2; pub const PR_SPEC_PRCTL: libc::c_int = 1 << 0; pub const PR_SPEC_ENABLE: libc::c_int = 1 << 1; pub const PR_SPEC_DISABLE: libc::c_int = 1 << 2; pub const PR_SPEC_FORCE_DISABLE: libc::c_int = 1 << 3; pub const PR_SPEC_DISABLE_NOEXEC: libc::c_int = 1 << 4; pub const PR_SET_IO_FLUSHER: libc::c_int = 57; pub const PR_GET_IO_FLUSHER: libc::c_int = 58; pub const PR_SET_MDWE: libc::c_int = 65; pub const PR_GET_MDWE: libc::c_int = 66; pub const PR_MDWE_REFUSE_EXEC_GAIN: libc::c_int = 1; // File capabilities constants #[cfg(feature = "std")] mod file { pub const VFS_CAP_FLAGS_EFFECTIVE: u32 = 0x00_0001; pub const VFS_CAP_REVISION_MASK: u32 = 0xFF00_0000; pub const VFS_CAP_FLAGS_MASK: u32 = !VFS_CAP_REVISION_MASK; pub const VFS_CAP_REVISION_1: u32 = 0x0100_0000; pub const XATTR_CAPS_SZ_1: usize = 12; pub const VFS_CAP_REVISION_2: u32 = 0x0200_0000; pub const XATTR_CAPS_SZ_2: usize = 20; pub const VFS_CAP_REVISION_3: u32 = 0x0300_0000; pub const XATTR_CAPS_SZ_3: usize = 24; pub const XATTR_CAPS_MAX_SIZE: usize = XATTR_CAPS_SZ_3; pub const XATTR_NAME_CAPS: &[u8] = b"security.capability\0"; } #[cfg(feature = "std")] pub use file::*; capctl-0.2.4/tarpaulin.toml000064400000000000000000000003061046102023000137700ustar 00000000000000[std_coverage] [no_std_coverage] no-default-features = true [serde_std_coverage] features = "serde" [serde_no_std_coverage] no-default-features = true features = "serde" [report] out = ["Html"]