use std::fmt;
use bitflags::bitflags;
use nix::errno::Errno;
use serde::{ser::SerializeSeq, Serialize, Serializer};
use crate::caps::{errors::CapsError, nr};
pub fn has_keepcaps() -> Result<bool, CapsError> {
let ret = Errno::result(unsafe { nix::libc::prctl(nr::PR_GET_KEEPCAPS, 0, 0, 0) })
.map_err(CapsError)?;
match ret {
0 => Ok(false),
_ => Ok(true),
}
}
pub fn set_keepcaps(keep_caps: bool) -> Result<(), CapsError> {
let flag = if keep_caps { 1 } else { 0 };
Errno::result(unsafe { nix::libc::prctl(nr::PR_SET_KEEPCAPS, flag, 0, 0) })
.map(drop)
.map_err(CapsError)
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SecureBits: u32 {
const SECBIT_NOROOT = 1 << 0;
const SECBIT_NOROOT_LOCKED = 1 << 1;
const SECBIT_NO_SETUID_FIXUP = 1 << 2;
const SECBIT_NO_SETUID_FIXUP_LOCKED = 1 << 3;
const SECBIT_KEEP_CAPS = 1 << 4;
const SECBIT_KEEP_CAPS_LOCKED = 1 << 5;
const SECBIT_NO_CAP_AMBIENT_RAISE = 1 << 6;
const SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED = 1 << 7;
const SECBIT_EXEC_RESTRICT_FILE = 1 << 8;
const SECBIT_EXEC_RESTRICT_FILE_LOCKED = 1 << 9;
const SECBIT_EXEC_DENY_INTERACTIVE = 1 << 10;
const SECBIT_EXEC_DENY_INTERACTIVE_LOCKED = 1 << 11;
const SECBIT_ALL_BASE =
Self::SECBIT_NOROOT.bits() |
Self::SECBIT_NO_SETUID_FIXUP.bits() |
Self::SECBIT_KEEP_CAPS.bits() |
Self::SECBIT_NO_CAP_AMBIENT_RAISE.bits() |
Self::SECBIT_EXEC_RESTRICT_FILE.bits() |
Self::SECBIT_EXEC_DENY_INTERACTIVE.bits();
const SECBIT_ALL_LOCK =
Self::SECBIT_NOROOT_LOCKED.bits() |
Self::SECBIT_NO_SETUID_FIXUP_LOCKED.bits() |
Self::SECBIT_KEEP_CAPS_LOCKED.bits() |
Self::SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED.bits() |
Self::SECBIT_EXEC_RESTRICT_FILE_LOCKED.bits() |
Self::SECBIT_EXEC_DENY_INTERACTIVE_LOCKED.bits();
const SECBIT_ALL_BASE_PRIV =
Self::SECBIT_NOROOT.bits() |
Self::SECBIT_NO_SETUID_FIXUP.bits() |
Self::SECBIT_KEEP_CAPS.bits() |
Self::SECBIT_NO_CAP_AMBIENT_RAISE.bits();
const SECBIT_ALL_LOCK_PRIV =
Self::SECBIT_NOROOT_LOCKED.bits() |
Self::SECBIT_NO_SETUID_FIXUP_LOCKED.bits() |
Self::SECBIT_KEEP_CAPS_LOCKED.bits() |
Self::SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED.bits();
const SECBIT_ALL_BASE_UNPRIV =
Self::SECBIT_EXEC_RESTRICT_FILE.bits() |
Self::SECBIT_EXEC_DENY_INTERACTIVE.bits();
const SECBIT_ALL_LOCK_UNPRIV =
Self::SECBIT_EXEC_RESTRICT_FILE_LOCKED.bits() |
Self::SECBIT_EXEC_DENY_INTERACTIVE_LOCKED.bits();
}
}
impl fmt::Display for SecureBits {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut securebits: Vec<&str> = vec![];
if self.contains(Self::SECBIT_NOROOT_LOCKED) {
securebits.push("secure-no-root-locked");
} else if self.contains(Self::SECBIT_NOROOT) {
securebits.push("secure-no-root");
}
if self.contains(Self::SECBIT_NO_SETUID_FIXUP_LOCKED) {
securebits.push("secure-no-setuid-fixup-locked");
} else if self.contains(Self::SECBIT_NO_SETUID_FIXUP) {
securebits.push("secure-no-setuid-fixup");
}
if self.contains(Self::SECBIT_KEEP_CAPS_LOCKED) {
securebits.push("secure-keep-caps-locked");
} else if self.contains(Self::SECBIT_KEEP_CAPS) {
securebits.push("secure-keep-caps");
}
if self.contains(Self::SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED) {
securebits.push("secure-no-ambient-raise-locked");
} else if self.contains(Self::SECBIT_NO_CAP_AMBIENT_RAISE) {
securebits.push("secure-no-ambient-raise");
}
if self.contains(Self::SECBIT_EXEC_RESTRICT_FILE_LOCKED) {
securebits.push("secure-exec-restrict-file-locked");
} else if self.contains(Self::SECBIT_EXEC_RESTRICT_FILE) {
securebits.push("secure-exec-restrict-file");
}
if self.contains(Self::SECBIT_EXEC_DENY_INTERACTIVE_LOCKED) {
securebits.push("secure-exec-deny-interactive-locked");
} else if self.contains(Self::SECBIT_EXEC_DENY_INTERACTIVE) {
securebits.push("secure-exec-deny-interactive");
}
write!(f, "{}", securebits.join(","))
}
}
impl Serialize for SecureBits {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.iter().count()))?;
for bit in self.iter() {
seq.serialize_element(&bit.to_string())?;
}
seq.end()
}
}
pub fn get_securebits() -> Result<SecureBits, CapsError> {
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe { libc::prctl(libc::PR_GET_SECUREBITS, 0, 0, 0, 0) })
.map(|r| r as u32)
.map(SecureBits::from_bits_retain)
.map_err(CapsError)
}
pub fn set_securebits(bits: SecureBits) -> Result<(), CapsError> {
Errno::result(unsafe { libc::prctl(libc::PR_SET_SECUREBITS, bits.bits(), 0, 0, 0) })
.map(drop)
.map_err(CapsError)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_securebits_1() {
let s = SecureBits::empty().to_string();
assert_eq!(s, "");
}
#[test]
fn test_securebits_2() {
let s = SecureBits::SECBIT_NOROOT.to_string();
assert_eq!(s, "secure-no-root");
}
#[test]
fn test_securebits_3() {
let s = SecureBits::SECBIT_NOROOT_LOCKED.to_string();
assert_eq!(s, "secure-no-root-locked");
}
#[test]
fn test_securebits_4() {
let s = SecureBits::SECBIT_KEEP_CAPS.to_string();
assert_eq!(s, "secure-keep-caps");
}
#[test]
fn test_securebits_5() {
let s = SecureBits::SECBIT_KEEP_CAPS_LOCKED.to_string();
assert_eq!(s, "secure-keep-caps-locked");
}
#[test]
fn test_securebits_6() {
let s = SecureBits::SECBIT_NO_SETUID_FIXUP.to_string();
assert_eq!(s, "secure-no-setuid-fixup");
}
#[test]
fn test_securebits_7() {
let s = SecureBits::SECBIT_NO_CAP_AMBIENT_RAISE.to_string();
assert_eq!(s, "secure-no-ambient-raise");
}
#[test]
fn test_securebits_8() {
let s = SecureBits::SECBIT_EXEC_RESTRICT_FILE.to_string();
assert_eq!(s, "secure-exec-restrict-file");
}
#[test]
fn test_securebits_9() {
let s = SecureBits::SECBIT_EXEC_DENY_INTERACTIVE.to_string();
assert_eq!(s, "secure-exec-deny-interactive");
}
#[test]
fn test_securebits_10() {
let bits = SecureBits::SECBIT_NOROOT | SecureBits::SECBIT_KEEP_CAPS;
let s = bits.to_string();
assert!(s.contains("secure-no-root"));
assert!(s.contains("secure-keep-caps"));
assert!(s.contains(','));
}
#[test]
fn test_securebits_11() {
assert!(SecureBits::SECBIT_ALL_BASE.contains(SecureBits::SECBIT_NOROOT));
}
#[test]
fn test_securebits_12() {
assert!(SecureBits::SECBIT_ALL_LOCK.contains(SecureBits::SECBIT_NOROOT_LOCKED));
}
#[test]
fn test_securebits_13() {
assert_eq!(SecureBits::SECBIT_NOROOT.bits(), 1 << 0);
assert_eq!(SecureBits::SECBIT_NOROOT_LOCKED.bits(), 1 << 1);
assert_eq!(SecureBits::SECBIT_KEEP_CAPS.bits(), 1 << 4);
}
}