From 1d89f98edf08d6d8ea54bd4af1bde20a5ff19c87 Mon Sep 17 00:00:00 2001 From: Praveen K Paladugu Date: Thu, 15 Feb 2024 23:36:23 +0000 Subject: [PATCH] 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 Signed-off-by: Wei Liu --- fuzz/fuzz_targets/http_api.rs | 1 + option_parser/src/lib.rs | 2 + src/main.rs | 8 +++ vmm/src/config.rs | 97 +++++++++++++++++++++++++++++++++++ vmm/src/landlock.rs | 1 + vmm/src/lib.rs | 2 + vmm/src/vm_config.rs | 7 +++ 7 files changed, 118 insertions(+) create mode 100644 vmm/src/landlock.rs diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index d801c90f1..553c338d5 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -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, diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index ac3dd3885..5a8399a94 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -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}"), } } } diff --git a/src/main.rs b/src/main.rs index 2639ea2a4..0cf5b0fa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); diff --git a/vmm/src/config.rs b/vmm/src/config.rs index b47ca37dd..a676dabe8 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -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 = std::result::Result; @@ -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>, } impl<'a> VmParams<'a> { @@ -539,6 +558,10 @@ impl<'a> VmParams<'a> { #[cfg(feature = "sev_snp")] let host_data = args.get_one::("host-data").map(|x| x as &str); let landlock_enable = args.get_flag("landlock"); + let landlock_config: Option> = args + .get_many::("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=,access=[rw]\""; + + pub fn parse(landlock_rule: &str) -> Result { + 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, @@ -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> = None; + if let Some(ll_config) = vm_params.landlock_config { + landlock_config = Some( + ll_config + .iter() + .map(|rule| LandlockConfig::parse(rule)) + .collect::>>()?, + ); + } + 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(()) + } } diff --git a/vmm/src/landlock.rs b/vmm/src/landlock.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/vmm/src/landlock.rs @@ -0,0 +1 @@ + diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index cd38060ca..fa0ea4a15 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -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, })) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index ea3a588cc..194d19f8f 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -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>, #[serde(default)] pub landlock_enable: bool, + pub landlock_config: Option>, }