mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-12-22 13:45:20 +00:00
vmm: Introduce Landlock module
This module introduces methods to apply Landlock LSM to cloud-hypervisor threads. Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
This commit is contained in:
parent
1d89f98edf
commit
af5a9677c8
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -1118,6 +1118,17 @@ dependencies = [
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "landlock"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dafb8a4afee64f167eb2b52d32f0eea002e41a7a6450e68c799c8ec3a81a634c"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -2450,6 +2461,7 @@ dependencies = [
|
||||
"hypervisor",
|
||||
"igvm",
|
||||
"igvm_defs",
|
||||
"landlock",
|
||||
"libc",
|
||||
"linux-loader",
|
||||
"log",
|
||||
|
@ -39,6 +39,7 @@ hex = { version = "0.4.3", optional = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
igvm = { version = "0.3.1", optional = true }
|
||||
igvm_defs = { version = "0.3.1", optional = true }
|
||||
landlock = "0.4.0"
|
||||
libc = "0.2.153"
|
||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||
log = "0.4.21"
|
||||
|
@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use crate::landlock::LandlockAccess;
|
||||
pub use crate::vm_config::*;
|
||||
use clap::ArgMatches;
|
||||
use option_parser::{
|
||||
@ -211,6 +212,8 @@ pub enum ValidationError {
|
||||
RestoreNetFdCountMismatch(String, usize, usize),
|
||||
/// Path provided in landlock-rules doesn't exist
|
||||
LandlockPathDoesNotExist(PathBuf),
|
||||
/// Access provided in landlock-rules in invalid
|
||||
InvalidLandlockAccess(String),
|
||||
}
|
||||
|
||||
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
||||
@ -369,6 +372,9 @@ impl fmt::Display for ValidationError {
|
||||
s.as_path()
|
||||
)
|
||||
}
|
||||
InvalidLandlockAccess(s) => {
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2361,6 +2367,8 @@ impl LandlockConfig {
|
||||
if !self.path.exists() {
|
||||
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
|
||||
}
|
||||
LandlockAccess::try_from(self.access.as_str())
|
||||
.map_err(|e| ValidationError::InvalidLandlockAccess(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,149 @@
|
||||
// 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),
|
||||
}
|
||||
|
||||
// https://docs.rs/landlock/latest/landlock/enum.ABI.html for more info on ABI
|
||||
static ABI: ABI = ABI::V3;
|
||||
|
||||
pub 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 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 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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user