#[path = "caps_xattr.rs"]
mod caps_xattr;
use std::path::PathBuf;
use crate::error::WhynoError;
pub const VFS_CAP_DATA: [u8; 20] = [
0x02, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
pub const XATTR_NAME: &[u8] = b"security.capability\0";
pub const VFS_CAP_SIZE: usize = 20;
pub fn caps_install() -> Result<(), WhynoError> {
let exe = locate_self()?;
caps_xattr::check_xattr_support(&exe)?;
caps_xattr::write_cap_xattr(&exe)?;
verify_cap_xattr(&exe)?;
println!("Installed CAP_DAC_READ_SEARCH on {}", exe.display());
Ok(())
}
pub fn caps_uninstall() -> Result<(), WhynoError> {
let exe = locate_self()?;
caps_xattr::remove_cap_xattr(&exe)?;
println!("Removed capabilities from {}", exe.display());
Ok(())
}
pub fn caps_check() -> Result<(), WhynoError> {
let exe = locate_self()?;
match caps_xattr::read_cap_xattr(&exe) {
Ok(data) if data == VFS_CAP_DATA => {
println!("CAP_DAC_READ_SEARCH is installed on {}", exe.display());
}
Ok(data) => {
println!(
"Capability xattr found but has unexpected content ({} bytes)",
data.len()
);
}
Err(msg) => {
println!("No capabilities installed: {msg}");
}
}
Ok(())
}
fn locate_self() -> Result<PathBuf, WhynoError> {
std::env::current_exe()
.map_err(|e| WhynoError::Caps(format!("cannot locate whyno binary: {e}")))
}
fn verify_cap_xattr(path: &std::path::Path) -> Result<(), WhynoError> {
let data = caps_xattr::read_cap_xattr(path)
.map_err(|e| WhynoError::Caps(format!("verification read-back failed: {e}")))?;
if data != VFS_CAP_DATA {
return Err(WhynoError::Caps(
"verification failed: xattr content does not match expected bytes".to_string(),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vfs_cap_data_is_20_bytes() {
assert_eq!(VFS_CAP_DATA.len(), 20);
}
#[test]
fn vfs_cap_magic_etc_is_correct() {
let magic = u32::from_le_bytes([
VFS_CAP_DATA[0],
VFS_CAP_DATA[1],
VFS_CAP_DATA[2],
VFS_CAP_DATA[3],
]);
assert_eq!(magic, 0x0200_0002);
}
#[test]
fn vfs_cap_permitted_has_dac_read_search() {
let permitted = u32::from_le_bytes([
VFS_CAP_DATA[4],
VFS_CAP_DATA[5],
VFS_CAP_DATA[6],
VFS_CAP_DATA[7],
]);
assert_eq!(permitted, 0x0000_0004);
}
#[test]
fn vfs_cap_inheritable_is_zero() {
for byte in &VFS_CAP_DATA[8..20] {
assert_eq!(*byte, 0);
}
}
#[test]
fn read_cap_xattr_no_caps_returns_error() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("nocaps.bin");
std::fs::write(&path, b"binary").unwrap();
let result = caps_xattr::read_cap_xattr(&path);
assert!(result.is_err(), "expected error for file with no caps xattr");
}
#[test]
fn check_xattr_support_on_temp_file_does_not_panic() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("test.bin");
std::fs::write(&path, b"data").unwrap();
let _result = caps_xattr::check_xattr_support(&path);
}
#[test]
fn read_cap_xattr_nonexistent_path_returns_error() {
let dir = tempfile::TempDir::new().unwrap();
let path = dir.path().join("ghost.bin");
let result = caps_xattr::read_cap_xattr(&path);
assert!(result.is_err(), "expected error for nonexistent path");
}
#[test]
fn path_to_cstring_nul_byte_returns_error() {
let path = std::path::Path::new("/tmp");
let _result = caps_xattr::check_xattr_support(path);
}
}