mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-19 13:30:56 +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",
|
"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]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -2450,6 +2461,7 @@ dependencies = [
|
|||||||
"hypervisor",
|
"hypervisor",
|
||||||
"igvm",
|
"igvm",
|
||||||
"igvm_defs",
|
"igvm_defs",
|
||||||
|
"landlock",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-loader",
|
"linux-loader",
|
||||||
"log",
|
"log",
|
||||||
|
@ -39,6 +39,7 @@ hex = { version = "0.4.3", optional = true }
|
|||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
igvm = { version = "0.3.1", optional = true }
|
igvm = { version = "0.3.1", optional = true }
|
||||||
igvm_defs = { version = "0.3.1", optional = true }
|
igvm_defs = { version = "0.3.1", optional = true }
|
||||||
|
landlock = "0.4.0"
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
linux-loader = { version = "0.11.0", features = ["bzimage", "elf", "pe"] }
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
|
use crate::landlock::LandlockAccess;
|
||||||
pub use crate::vm_config::*;
|
pub use crate::vm_config::*;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use option_parser::{
|
use option_parser::{
|
||||||
@ -211,6 +212,8 @@ pub enum ValidationError {
|
|||||||
RestoreNetFdCountMismatch(String, usize, usize),
|
RestoreNetFdCountMismatch(String, usize, usize),
|
||||||
/// Path provided in landlock-rules doesn't exist
|
/// Path provided in landlock-rules doesn't exist
|
||||||
LandlockPathDoesNotExist(PathBuf),
|
LandlockPathDoesNotExist(PathBuf),
|
||||||
|
/// Access provided in landlock-rules in invalid
|
||||||
|
InvalidLandlockAccess(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
||||||
@ -369,6 +372,9 @@ impl fmt::Display for ValidationError {
|
|||||||
s.as_path()
|
s.as_path()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
InvalidLandlockAccess(s) => {
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2361,6 +2367,8 @@ impl LandlockConfig {
|
|||||||
if !self.path.exists() {
|
if !self.path.exists() {
|
||||||
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
|
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
|
||||||
}
|
}
|
||||||
|
LandlockAccess::try_from(self.access.as_str())
|
||||||
|
.map_err(|e| ValidationError::InvalidLandlockAccess(e.to_string()))?;
|
||||||
Ok(())
|
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