mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2025-03-20 07:58:55 +00:00
vmm: Introduce landlock-rules cmdline param
Users can use this parameter to pass extra paths that 'vmm' and its child threads can use at runtime. Hotplug is the primary usecase for this parameter. In order to hotplug devices that use local files: disks, memory zones, pmem devices etc, users can use this option to pass the path/s that will be used during hotplug while starting cloud-hypervisor. Doing this will allow landlock to add required rules to grant access to these paths when cloud-hypervisor process starts. Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com> Signed-off-by: Wei Liu <liuwe@microsoft.com>
This commit is contained in:
parent
287dbd4fc9
commit
1d89f98edf
@ -191,6 +191,7 @@ impl RequestHandler for StubApiRequestHandler {
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_config: None,
|
||||
})),
|
||||
state: VmState::Running,
|
||||
memory_actual_size: 0,
|
||||
|
@ -23,6 +23,7 @@ pub enum OptionParserError {
|
||||
UnknownOption(String),
|
||||
InvalidSyntax(String),
|
||||
Conversion(String, String),
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionParserError {
|
||||
@ -33,6 +34,7 @@ impl fmt::Display for OptionParserError {
|
||||
OptionParserError::Conversion(field, value) => {
|
||||
write!(f, "unable to convert {value} for {field}")
|
||||
}
|
||||
OptionParserError::InvalidValue(s) => write!(f, "invalid value: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,6 +282,13 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
|
||||
.default_value("false")
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("landlock-rules")
|
||||
.long("landlock-rules")
|
||||
.help(config::LandlockConfig::SYNTAX)
|
||||
.num_args(1..)
|
||||
.group("vm-config"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("net")
|
||||
.long("net")
|
||||
@ -1044,6 +1051,7 @@ mod unit_tests {
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_config: None,
|
||||
};
|
||||
|
||||
assert_eq!(expected_vm_config, result_vm_config);
|
||||
|
@ -110,6 +110,10 @@ pub enum Error {
|
||||
ParseTpm(OptionParserError),
|
||||
/// Missing path for TPM device
|
||||
ParseTpmPathMissing,
|
||||
/// Error parsing Landlock rules
|
||||
ParseLandlockRules(OptionParserError),
|
||||
/// Missing fields in Landlock rules
|
||||
ParseLandlockMissingFields,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Error)]
|
||||
@ -205,6 +209,8 @@ pub enum ValidationError {
|
||||
RestoreMissingRequiredNetId(String),
|
||||
/// Number of FDs passed during Restore are incorrect to the NetConfig
|
||||
RestoreNetFdCountMismatch(String, usize, usize),
|
||||
/// Path provided in landlock-rules doesn't exist
|
||||
LandlockPathDoesNotExist(PathBuf),
|
||||
}
|
||||
|
||||
type ValidationResult<T> = std::result::Result<T, ValidationError>;
|
||||
@ -356,6 +362,13 @@ impl fmt::Display for ValidationError {
|
||||
"Number of Net FDs passed for '{s}' during Restore: {u1}. Expected: {u2}"
|
||||
)
|
||||
}
|
||||
LandlockPathDoesNotExist(s) => {
|
||||
write!(
|
||||
f,
|
||||
"Path {:?} provided in landlock-rules does not exist",
|
||||
s.as_path()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,6 +434,11 @@ impl fmt::Display for Error {
|
||||
ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"),
|
||||
ParseTpm(o) => write!(f, "Error parsing --tpm: {o}"),
|
||||
ParseTpmPathMissing => write!(f, "Error parsing --tpm: path missing"),
|
||||
ParseLandlockRules(o) => write!(f, "Error parsing --landlock-rules: {o}"),
|
||||
ParseLandlockMissingFields => write!(
|
||||
f,
|
||||
"Error parsing --landlock-rules: path/access field missing"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -473,6 +491,7 @@ pub struct VmParams<'a> {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub host_data: Option<&'a str>,
|
||||
pub landlock_enable: bool,
|
||||
pub landlock_config: Option<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> VmParams<'a> {
|
||||
@ -539,6 +558,10 @@ impl<'a> VmParams<'a> {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
let host_data = args.get_one::<String>("host-data").map(|x| x as &str);
|
||||
let landlock_enable = args.get_flag("landlock");
|
||||
let landlock_config: Option<Vec<&str>> = args
|
||||
.get_many::<String>("landlock-rules")
|
||||
.map(|x| x.map(|y| y as &str).collect());
|
||||
|
||||
VmParams {
|
||||
cpus,
|
||||
memory,
|
||||
@ -577,6 +600,7 @@ impl<'a> VmParams<'a> {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
host_data,
|
||||
landlock_enable,
|
||||
landlock_config,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2304,6 +2328,43 @@ impl TpmConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl LandlockConfig {
|
||||
pub const SYNTAX: &'static str = "Landlock parameters \
|
||||
\"path=<path/to/{file/dir}>,access=[rw]\"";
|
||||
|
||||
pub fn parse(landlock_rule: &str) -> Result<Self> {
|
||||
let mut parser = OptionParser::new();
|
||||
parser.add("path").add("access");
|
||||
parser
|
||||
.parse(landlock_rule)
|
||||
.map_err(Error::ParseLandlockRules)?;
|
||||
|
||||
let path = parser
|
||||
.get("path")
|
||||
.map(PathBuf::from)
|
||||
.ok_or(Error::ParseLandlockMissingFields)?;
|
||||
|
||||
let access = parser
|
||||
.get("access")
|
||||
.ok_or(Error::ParseLandlockMissingFields)?;
|
||||
|
||||
if access.chars().count() > 2 {
|
||||
return Err(Error::ParseLandlockRules(OptionParserError::InvalidValue(
|
||||
access.to_string(),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(LandlockConfig { path, access })
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> ValidationResult<()> {
|
||||
if !self.path.exists() {
|
||||
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VmConfig {
|
||||
fn validate_identifier(
|
||||
id_list: &mut BTreeSet<String>,
|
||||
@ -2656,6 +2717,12 @@ impl VmConfig {
|
||||
.map(|p| p.iommu_segments.is_some())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(landlock_configs) = &self.landlock_config {
|
||||
for landlock_config in landlock_configs {
|
||||
landlock_config.validate()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(id_list)
|
||||
}
|
||||
|
||||
@ -2826,6 +2893,16 @@ impl VmConfig {
|
||||
#[cfg(feature = "guest_debug")]
|
||||
let gdb = vm_params.gdb;
|
||||
|
||||
let mut landlock_config: Option<Vec<LandlockConfig>> = None;
|
||||
if let Some(ll_config) = vm_params.landlock_config {
|
||||
landlock_config = Some(
|
||||
ll_config
|
||||
.iter()
|
||||
.map(|rule| LandlockConfig::parse(rule))
|
||||
.collect::<Result<Vec<LandlockConfig>>>()?,
|
||||
);
|
||||
}
|
||||
|
||||
let mut config = VmConfig {
|
||||
cpus: CpusConfig::parse(vm_params.cpus)?,
|
||||
memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
|
||||
@ -2858,6 +2935,7 @@ impl VmConfig {
|
||||
tpm,
|
||||
preserved_fds: None,
|
||||
landlock_enable: vm_params.landlock_enable,
|
||||
landlock_config,
|
||||
};
|
||||
config.validate().map_err(Error::Validation)?;
|
||||
Ok(config)
|
||||
@ -2984,6 +3062,7 @@ impl Clone for VmConfig {
|
||||
.as_ref()
|
||||
// SAFETY: FFI call with valid FDs
|
||||
.map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()),
|
||||
landlock_config: self.landlock_config.clone(),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
@ -3783,6 +3862,7 @@ mod tests {
|
||||
},
|
||||
]),
|
||||
landlock_enable: false,
|
||||
landlock_config: None,
|
||||
};
|
||||
|
||||
let valid_config = RestoreConfig {
|
||||
@ -3972,6 +4052,7 @@ mod tests {
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_config: None,
|
||||
};
|
||||
|
||||
assert!(valid_config.validate().is_ok());
|
||||
@ -4529,4 +4610,20 @@ mod tests {
|
||||
}
|
||||
let _still_valid_config = still_valid_config.clone();
|
||||
}
|
||||
#[test]
|
||||
fn test_landlock_parsing() -> Result<()> {
|
||||
// should not be empty
|
||||
assert!(LandlockConfig::parse("").is_err());
|
||||
// access should not be empty
|
||||
assert!(LandlockConfig::parse("path=/dir/path1").is_err());
|
||||
assert!(LandlockConfig::parse("path=/dir/path1,access=rwr").is_err());
|
||||
assert_eq!(
|
||||
LandlockConfig::parse("path=/dir/path1,access=rw")?,
|
||||
LandlockConfig {
|
||||
path: PathBuf::from("/dir/path1"),
|
||||
access: "rw".to_string(),
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
1
vmm/src/landlock.rs
Normal file
1
vmm/src/landlock.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -75,6 +75,7 @@ mod gdb;
|
||||
#[cfg(feature = "igvm")]
|
||||
mod igvm;
|
||||
pub mod interrupt;
|
||||
pub mod landlock;
|
||||
pub mod memory_manager;
|
||||
pub mod migration;
|
||||
mod pci_segment;
|
||||
@ -2190,6 +2191,7 @@ mod unit_tests {
|
||||
tpm: None,
|
||||
preserved_fds: None,
|
||||
landlock_enable: false,
|
||||
landlock_config: None,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -598,6 +598,12 @@ pub struct TpmConfig {
|
||||
pub socket: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct LandlockConfig {
|
||||
pub path: PathBuf,
|
||||
pub access: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct VmConfig {
|
||||
#[serde(default)]
|
||||
@ -647,4 +653,5 @@ pub struct VmConfig {
|
||||
pub preserved_fds: Option<Vec<i32>>,
|
||||
#[serde(default)]
|
||||
pub landlock_enable: bool,
|
||||
pub landlock_config: Option<Vec<LandlockConfig>>,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user