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:
Praveen K Paladugu 2024-02-12 19:06:43 +00:00 committed by Liu Wei
parent 1d89f98edf
commit af5a9677c8
4 changed files with 169 additions and 0 deletions

12
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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(())
}
}

View File

@ -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());
}