Files
cloud-hypervisor/vmm/src/landlock.rs
Praveen K Paladugu 7f6731cd05 vmm: Limit the visibility of Landlock objects
Limit the visibility of objects and methods introduced by Landlock
implementation.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-07-06 04:42:58 +00:00

154 lines
4.5 KiB
Rust

// Copyright © 2024 Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
use landlock::make_bitflags;
use landlock::{
path_beneath_rules, Access, AccessFs, BitFlags, Ruleset, RulesetAttr, RulesetCreated,
RulesetCreatedAttr, RulesetError, ABI,
};
use std::convert::TryFrom;
use std::io::Error as IoError;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum LandlockError {
/// All RulesetErrors from Landlock library are wrapped in this error
#[error("Error creating/adding/restricting ruleset: {0}")]
ManageRuleset(#[source] RulesetError),
/// Error opening path
#[error("Error opening path: {0}")]
OpenPath(#[source] IoError),
/// Invalid Landlock access
#[error("Invalid Landlock access: {0}")]
InvalidLandlockAccess(String),
/// Invalid Path
#[error("Invalid path")]
InvalidPath,
}
// https://docs.rs/landlock/latest/landlock/enum.ABI.html for more info on ABI
static ABI: ABI = ABI::V3;
pub(crate) struct LandlockAccess {
access: BitFlags<AccessFs>,
}
impl TryFrom<&str> for LandlockAccess {
type Error = LandlockError;
fn try_from(s: &str) -> Result<LandlockAccess, LandlockError> {
if s.is_empty() {
return Err(LandlockError::InvalidLandlockAccess(
"Access cannot be empty".to_string(),
));
}
let mut access = BitFlags::<AccessFs>::empty();
for c in s.chars() {
match c {
'r' => access |= AccessFs::from_read(ABI),
'w' => access |= AccessFs::from_write(ABI),
_ => {
return Err(LandlockError::InvalidLandlockAccess(
format!("Invalid access: {c}").to_string(),
))
}
};
}
Ok(LandlockAccess { access })
}
}
pub struct Landlock {
ruleset: RulesetCreated,
}
impl Landlock {
pub fn new() -> Result<Landlock, LandlockError> {
let file_access = AccessFs::from_all(ABI);
let def_ruleset = Ruleset::default()
.handle_access(file_access)
.map_err(LandlockError::ManageRuleset)?;
// By default, rulesets are created in `BestEffort` mode. This lets Landlock
// to enable all the supported rules and silently ignore the unsupported ones.
let ruleset = def_ruleset.create().map_err(LandlockError::ManageRuleset)?;
Ok(Landlock { ruleset })
}
pub(crate) fn add_rule(
&mut self,
path: PathBuf,
access: BitFlags<AccessFs>,
) -> Result<(), LandlockError> {
// path_beneath_rules in landlock crate handles file and directory access rules.
// Incoming path/s are passed to path_beneath_rules, so that we don't
// have to worry about the type of the path.
let paths = vec![path.clone()];
let path_beneath_rules = path_beneath_rules(paths, access);
self.ruleset
.as_mut()
.add_rules(path_beneath_rules)
.map_err(LandlockError::ManageRuleset)?;
Ok(())
}
pub(crate) fn add_rule_with_access(
&mut self,
path: PathBuf,
access: &str,
) -> Result<(), LandlockError> {
self.add_rule(path, LandlockAccess::try_from(access)?.access)?;
Ok(())
}
pub fn restrict_self(self) -> Result<(), LandlockError> {
self.ruleset
.restrict_self()
.map_err(LandlockError::ManageRuleset)?;
Ok(())
}
}
#[test]
fn test_try_from_access() {
// These access rights could change in future versions of Landlock. Listing
// them here explicitly to raise their visibility during code reviews.
let read_access = make_bitflags!(AccessFs::{
Execute
| ReadFile
| ReadDir
});
let write_access = make_bitflags!(AccessFs::{
WriteFile
| RemoveDir
| RemoveFile
| MakeChar
| MakeDir
| MakeReg
| MakeSock
| MakeFifo
| MakeBlock
| MakeSym
| Refer
| Truncate
});
let landlock_access = LandlockAccess::try_from("rw").unwrap();
assert!(landlock_access.access == read_access | write_access);
let landlock_access = LandlockAccess::try_from("r").unwrap();
assert!(landlock_access.access == read_access);
let landlock_access = LandlockAccess::try_from("w").unwrap();
assert!(landlock_access.access == write_access);
assert!(LandlockAccess::try_from("").is_err());
}