diff --git a/docs/landlock.md b/docs/landlock.md index 9d0ad4edc..4067151c7 100644 --- a/docs/landlock.md +++ b/docs/landlock.md @@ -30,7 +30,10 @@ Linux kernel confirms Landlock support with above message in dmesg. ## Implementation Details To enable Landlock, Cloud-Hypervisor process needs the full list of files it -needs to access over its lifetime. Landlock is enabled in the `vm_create` stage. +needs to access over its lifetime. Most of these files are received as VM +Configuration (`struct VmConfig`). Landlock is enabled in `vm_create` stage, as +this is the earliest stage in guest boot sequence which has access to guest's +VM Configuration. ## Enable Landlock diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index a3f328630..553c338d5 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -190,6 +190,8 @@ impl RequestHandler for StubApiRequestHandler { platform: None, tpm: None, preserved_fds: None, + landlock_enable: false, + landlock_config: None, })), state: VmState::Running, memory_actual_size: 0, diff --git a/src/main.rs b/src/main.rs index a724de52d..451c9985c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -285,14 +285,14 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String ) .action(ArgAction::SetTrue) .default_value("false") - .group("vmm-config"), + .group("vm-config"), ) .arg( Arg::new("landlock-rules") .long("landlock-rules") .help(config::LandlockConfig::SYNTAX) .num_args(1..) - .group("vmm-config"), + .group("vm-config"), ) .arg( Arg::new("net") @@ -657,31 +657,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?; let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?; - let landlock_enable = cmd_arguments.get_flag("landlock"); - let landlock_config_str_vec: Option> = cmd_arguments - .get_many::("landlock-rules") - .map(|x| x.map(|y| y as &str).collect()); - - let landlock_config = if let Some(str_vec) = landlock_config_str_vec { - Some( - str_vec - .into_iter() - .map(config::LandlockConfig::parse) - .collect::>>() - .map_err(Error::ParsingConfig)?, - ) - } else { - None - }; - - if let Some(lc) = landlock_config.as_ref() { - for c in lc.iter() { - c.validate() - .map_err(config::Error::Validation) - .map_err(Error::ParsingConfig)?; - } - } #[allow(unused_mut)] let mut event_monitor = cmd_arguments @@ -779,7 +755,6 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result, Error> { &seccomp_action, hypervisor, landlock_enable, - landlock_config, ) .map_err(Error::StartVmmThread)?; @@ -1090,6 +1065,8 @@ mod unit_tests { platform: None, 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 e54fdd938..6a9665e66 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -496,6 +496,8 @@ pub struct VmParams<'a> { pub igvm: Option<&'a str>, #[cfg(feature = "sev_snp")] pub host_data: Option<&'a str>, + pub landlock_enable: bool, + pub landlock_config: Option>, } impl<'a> VmParams<'a> { @@ -561,6 +563,10 @@ impl<'a> VmParams<'a> { let igvm = args.get_one::("igvm").map(|x| x as &str); #[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, @@ -599,6 +605,8 @@ impl<'a> VmParams<'a> { igvm, #[cfg(feature = "sev_snp")] host_data, + landlock_enable, + landlock_config, } } } @@ -2326,12 +2334,6 @@ impl TpmConfig { } } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct LandlockConfig { - pub path: PathBuf, - pub access: String, -} - impl LandlockConfig { pub const SYNTAX: &'static str = "Landlock parameters \ \"path=,access=[rw]\""; @@ -2723,6 +2725,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) } @@ -2893,6 +2901,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)?, @@ -2924,6 +2942,8 @@ impl VmConfig { platform, tpm, preserved_fds: None, + landlock_enable: vm_params.landlock_enable, + landlock_config, }; config.validate().map_err(Error::Validation)?; Ok(config) @@ -3050,6 +3070,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 } } @@ -3848,6 +3869,8 @@ mod tests { ..net_fixture() }, ]), + landlock_enable: false, + landlock_config: None, }; let valid_config = RestoreConfig { @@ -4036,6 +4059,8 @@ mod tests { platform: None, tpm: None, preserved_fds: None, + landlock_enable: false, + landlock_config: None, }; assert!(valid_config.validate().is_ok()); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index e07ee7f32..32e1d9e24 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -12,11 +12,8 @@ use crate::api::{ ApiRequest, ApiResponse, RequestHandler, VmInfoResponse, VmReceiveMigrationData, VmSendMigrationData, VmmPingResponse, }; -#[cfg(target_arch = "x86_64")] -use crate::config::DebugConsoleConfig; use crate::config::{ - add_to_config, ConsoleConfig, DeviceConfig, DiskConfig, FsConfig, LandlockConfig, - MemoryZoneConfig, NetConfig, PayloadConfig, PmemConfig, RestoreConfig, RngConfig, TpmConfig, + add_to_config, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))] @@ -42,7 +39,6 @@ use serde::ser::{SerializeStruct, Serializer}; use serde::{Deserialize, Serialize}; use signal_hook::iterator::{Handle, Signals}; use std::collections::HashMap; -use std::fs; use std::fs::File; use std::io; use std::io::{stdout, Read, Write}; @@ -417,7 +413,6 @@ pub fn start_vmm_thread( seccomp_action: &SeccompAction, hypervisor: Arc, landlock_enable: bool, - landlock_config: Option>, ) -> Result { #[cfg(feature = "guest_debug")] let gdb_hw_breakpoints = hypervisor.get_guest_debug_hw_bps(); @@ -456,8 +451,6 @@ pub fn start_vmm_thread( vmm_seccomp_action, hypervisor, exit_event, - landlock_enable, - landlock_config, )?; vmm.setup_signal_handler(landlock_enable)?; @@ -585,8 +578,6 @@ pub struct Vmm { original_termios_opt: Arc>>, console_resize_pipe: Option, console_info: Option, - landlock_enable: bool, - landlock_config: Option>, } impl Vmm { @@ -693,8 +684,6 @@ impl Vmm { seccomp_action: SeccompAction, hypervisor: Arc, exit_evt: EventFd, - landlock_enable: bool, - landlock_config: Option>, ) -> Result { let mut epoll = EpollContext::new().map_err(Error::Epoll)?; let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; @@ -741,8 +730,6 @@ impl Vmm { original_termios_opt: Arc::new(Mutex::new(None)), console_resize_pipe: None, console_info: None, - landlock_enable, - landlock_config, }) } @@ -779,12 +766,15 @@ impl Vmm { MigratableError::MigrateReceive(anyhow!("Error creating console devices: {:?}", e)) })?); - if self.landlock_enable { - apply_landlock( - &self.vm_config.as_ref().unwrap().clone(), - &self.landlock_config, - ) - .map_err(|e| { + if self + .vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .landlock_enable + { + apply_landlock(self.vm_config.as_ref().unwrap().clone()).map_err(|e| { MigratableError::MigrateReceive(anyhow!("Error applying landlock: {:?}", e)) })?; } @@ -1243,236 +1233,8 @@ impl Vmm { } } -pub type LandlockResult = result::Result; -/// Trait to apply Landlock on VmConfig elements -pub(crate) trait ApplyLandlock { - /// Apply Landlock rules to file paths - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()>; -} - -impl ApplyLandlock for MemoryZoneConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; - } - Ok(()) - } -} - -impl ApplyLandlock for DiskConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - if let Some(path) = &self.path { - landlock.add_rule_with_access(path.to_path_buf(), "rw")?; - } - Ok(()) - } -} - -impl ApplyLandlock for RngConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - // Rng Path only need read access - landlock.add_rule_with_access(self.src.to_path_buf(), "r")?; - Ok(()) - } -} - -impl ApplyLandlock for FsConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for PmemConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.file.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for ConsoleConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; - } - if let Some(socket) = &self.socket { - landlock.add_rule_with_access(socket.to_path_buf(), "rw")?; - } - Ok(()) - } -} - -#[cfg(target_arch = "x86_64")] -impl ApplyLandlock for DebugConsoleConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - if let Some(file) = &self.file { - landlock.add_rule_with_access(file.to_path_buf(), "rw")?; - } - Ok(()) - } -} - -impl ApplyLandlock for DeviceConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - let device_path = fs::read_link(self.path.as_path()).map_err(LandlockError::OpenPath)?; - let iommu_group = device_path.file_name(); - let iommu_group_str = iommu_group - .ok_or(LandlockError::InvalidPath)? - .to_str() - .ok_or(LandlockError::InvalidPath)?; - - let vfio_group_path = "/dev/vfio/".to_owned() + iommu_group_str; - landlock.add_rule_with_access(vfio_group_path.into(), "rw")?; - - Ok(()) - } -} - -impl ApplyLandlock for UserDeviceConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for VdpaConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.path.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for VsockConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for PayloadConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - // Payload only needs read access - if let Some(firmware) = &self.firmware { - landlock.add_rule_with_access(firmware.to_path_buf(), "r")?; - } - - if let Some(kernel) = &self.kernel { - landlock.add_rule_with_access(kernel.to_path_buf(), "r")?; - } - - if let Some(initramfs) = &self.initramfs { - landlock.add_rule_with_access(initramfs.to_path_buf(), "r")?; - } - - #[cfg(feature = "igvm")] - if let Some(igvm) = &self.igvm { - landlock.add_rule_with_access(igvm.to_path_buf(), "r")?; - } - - Ok(()) - } -} - -impl ApplyLandlock for TpmConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; - Ok(()) - } -} - -impl ApplyLandlock for LandlockConfig { - fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { - landlock.add_rule_with_access(self.path.to_path_buf(), self.access.clone().as_str())?; - Ok(()) - } -} - -fn apply_landlock( - vm_config: &Arc>, - landlock_config: &Option>, -) -> LandlockResult<()> { - let vm_config = vm_config.lock().unwrap(); - let mut landlock = Landlock::new()?; - - if let Some(mem_zones) = &vm_config.memory.zones { - for zone in mem_zones.iter() { - zone.apply_landlock(&mut landlock)?; - } - } - - let disks = &vm_config.disks; - if let Some(disks) = disks { - for disk in disks.iter() { - disk.apply_landlock(&mut landlock)?; - } - } - - vm_config.rng.apply_landlock(&mut landlock)?; - - if let Some(fs_configs) = &vm_config.fs { - for fs_config in fs_configs.iter() { - fs_config.apply_landlock(&mut landlock)?; - } - } - - if let Some(pmem_configs) = &vm_config.pmem { - for pmem_config in pmem_configs.iter() { - pmem_config.apply_landlock(&mut landlock)?; - } - } - - vm_config.console.apply_landlock(&mut landlock)?; - vm_config.serial.apply_landlock(&mut landlock)?; - - #[cfg(target_arch = "x86_64")] - { - vm_config.debug_console.apply_landlock(&mut landlock)?; - } - - if let Some(devices) = &vm_config.devices { - landlock.add_rule_with_access("/dev/vfio/vfio".into(), "rw")?; - - for device in devices.iter() { - device.apply_landlock(&mut landlock)?; - } - } - - if let Some(user_devices) = &vm_config.user_devices { - for user_devices in user_devices.iter() { - user_devices.apply_landlock(&mut landlock)?; - } - } - - if let Some(vdpa_configs) = &vm_config.vdpa { - for vdpa_config in vdpa_configs.iter() { - vdpa_config.apply_landlock(&mut landlock)?; - } - } - - if let Some(vsock_config) = &vm_config.vsock { - vsock_config.apply_landlock(&mut landlock)?; - } - - if let Some(payload) = &vm_config.payload { - payload.apply_landlock(&mut landlock)?; - } - - if let Some(tpm_config) = &vm_config.tpm { - tpm_config.apply_landlock(&mut landlock)?; - } - - if vm_config.net.is_some() { - landlock.add_rule_with_access("/dev/net/tun".into(), "rw")?; - } - - if let Some(landlock_configs) = landlock_config { - for landlock_config in landlock_configs.iter() { - landlock_config.apply_landlock(&mut landlock)?; - } - } - - landlock.restrict_self()?; - +fn apply_landlock(vm_config: Arc>) -> result::Result<(), LandlockError> { + vm_config.lock().unwrap().apply_landlock()?; Ok(()) } @@ -1485,12 +1247,16 @@ impl RequestHandler for Vmm { self.console_info = Some(pre_create_console_devices(self).map_err(VmError::CreateConsoleDevices)?); - if self.landlock_enable { - apply_landlock( - &self.vm_config.as_ref().unwrap().clone(), - &self.landlock_config, - ) - .map_err(VmError::ApplyLandlock)?; + if self + .vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .landlock_enable + { + apply_landlock(self.vm_config.as_ref().unwrap().clone()) + .map_err(VmError::ApplyLandlock)?; } Ok(()) } else { @@ -1675,12 +1441,16 @@ impl RequestHandler for Vmm { )?; self.vm = Some(vm); - if self.landlock_enable { - apply_landlock( - &self.vm_config.as_ref().unwrap().clone(), - &self.landlock_config, - ) - .map_err(VmError::ApplyLandlock)?; + if self + .vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .landlock_enable + { + apply_landlock(self.vm_config.as_ref().unwrap().clone()) + .map_err(VmError::ApplyLandlock)?; } // Now we can restore the rest of the VM. @@ -2394,8 +2164,6 @@ mod unit_tests { SeccompAction::Allow, hypervisor::new().unwrap(), EventFd::new(EFD_NONBLOCK).unwrap(), - false, - None, ) .unwrap() } @@ -2474,6 +2242,8 @@ mod unit_tests { platform: None, 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 1f323df06..51de9af05 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -2,11 +2,20 @@ // // SPDX-License-Identifier: Apache-2.0 // +use crate::{landlock::LandlockError, Landlock}; use net_util::MacAddr; use serde::{Deserialize, Serialize}; -use std::{net::Ipv4Addr, path::PathBuf}; +use std::{fs, net::Ipv4Addr, path::PathBuf, result}; use virtio_devices::RateLimiterConfig; +pub type LandlockResult = result::Result; + +/// Trait to apply Landlock on VmConfig elements +pub(crate) trait ApplyLandlock { + /// Apply Landlock rules to file paths + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()>; +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct CpuAffinity { pub vcpu: u8, @@ -132,6 +141,15 @@ pub struct MemoryZoneConfig { pub prefault: bool, } +impl ApplyLandlock for MemoryZoneConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if let Some(file) = &self.file { + landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + } + Ok(()) + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)] pub enum HotplugMethod { #[default] @@ -245,6 +263,15 @@ pub struct DiskConfig { pub queue_affinity: Option>, } +impl ApplyLandlock for DiskConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if let Some(path) = &self.path { + landlock.add_rule_with_access(path.to_path_buf(), "rw")?; + } + Ok(()) + } +} + pub const DEFAULT_DISK_NUM_QUEUES: usize = 1; pub fn default_diskconfig_num_queues() -> usize { @@ -378,6 +405,14 @@ impl Default for RngConfig { } } +impl ApplyLandlock for RngConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + // Rng Path only need read access + landlock.add_rule_with_access(self.src.to_path_buf(), "r")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct BalloonConfig { pub size: u64, @@ -411,6 +446,13 @@ pub fn default_fsconfig_queue_size() -> u16 { 1024 } +impl ApplyLandlock for FsConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PmemConfig { pub file: PathBuf, @@ -426,6 +468,13 @@ pub struct PmemConfig { pub pci_segment: u16, } +impl ApplyLandlock for PmemConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.file.to_path_buf(), "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub enum ConsoleOutputMode { Off, @@ -450,6 +499,18 @@ pub fn default_consoleconfig_file() -> Option { None } +impl ApplyLandlock for ConsoleConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if let Some(file) = &self.file { + landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + } + if let Some(socket) = &self.socket { + landlock.add_rule_with_access(socket.to_path_buf(), "rw")?; + } + Ok(()) + } +} + #[cfg(target_arch = "x86_64")] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DebugConsoleConfig { @@ -470,6 +531,15 @@ impl Default for DebugConsoleConfig { } } } +#[cfg(target_arch = "x86_64")] +impl ApplyLandlock for DebugConsoleConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + if let Some(file) = &self.file { + landlock.add_rule_with_access(file.to_path_buf(), "rw")?; + } + Ok(()) + } +} #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct DeviceConfig { @@ -484,6 +554,22 @@ pub struct DeviceConfig { pub x_nv_gpudirect_clique: Option, } +impl ApplyLandlock for DeviceConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + let device_path = fs::read_link(self.path.as_path()).map_err(LandlockError::OpenPath)?; + let iommu_group = device_path.file_name(); + let iommu_group_str = iommu_group + .ok_or(LandlockError::InvalidPath)? + .to_str() + .ok_or(LandlockError::InvalidPath)?; + + let vfio_group_path = "/dev/vfio/".to_owned() + iommu_group_str; + landlock.add_rule_with_access(vfio_group_path.into(), "rw")?; + + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct UserDeviceConfig { pub socket: PathBuf, @@ -493,6 +579,13 @@ pub struct UserDeviceConfig { pub pci_segment: u16, } +impl ApplyLandlock for UserDeviceConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VdpaConfig { pub path: PathBuf, @@ -510,6 +603,13 @@ pub fn default_vdpaconfig_num_queues() -> usize { 1 } +impl ApplyLandlock for VdpaConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.path.to_path_buf(), "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VsockConfig { pub cid: u32, @@ -522,6 +622,13 @@ pub struct VsockConfig { pub pci_segment: u16, } +impl ApplyLandlock for VsockConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + Ok(()) + } +} + #[cfg(target_arch = "x86_64")] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct SgxEpcConfig { @@ -575,6 +682,30 @@ pub struct PayloadConfig { pub host_data: Option, } +impl ApplyLandlock for PayloadConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + // Payload only needs read access + if let Some(firmware) = &self.firmware { + landlock.add_rule_with_access(firmware.to_path_buf(), "r")?; + } + + if let Some(kernel) = &self.kernel { + landlock.add_rule_with_access(kernel.to_path_buf(), "r")?; + } + + if let Some(initramfs) = &self.initramfs { + landlock.add_rule_with_access(initramfs.to_path_buf(), "r")?; + } + + #[cfg(feature = "igvm")] + if let Some(igvm) = &self.igvm { + landlock.add_rule_with_access(igvm.to_path_buf(), "r")?; + } + + Ok(()) + } +} + pub fn default_serial() -> ConsoleConfig { ConsoleConfig { file: None, @@ -598,6 +729,26 @@ pub struct TpmConfig { pub socket: PathBuf, } +impl ApplyLandlock for TpmConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.socket.to_path_buf(), "rw")?; + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct LandlockConfig { + pub path: PathBuf, + pub access: String, +} + +impl ApplyLandlock for LandlockConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(self.path.to_path_buf(), self.access.clone().as_str())?; + Ok(()) + } +} + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VmConfig { #[serde(default)] @@ -645,4 +796,94 @@ pub struct VmConfig { // valid, and will be closed when the holding VmConfig instance is destroyed. #[serde(skip)] pub preserved_fds: Option>, + #[serde(default)] + pub landlock_enable: bool, + pub landlock_config: Option>, +} + +impl VmConfig { + pub(crate) fn apply_landlock(&self) -> LandlockResult<()> { + let mut landlock = Landlock::new()?; + + if let Some(mem_zones) = &self.memory.zones { + for zone in mem_zones.iter() { + zone.apply_landlock(&mut landlock)?; + } + } + + let disks = &self.disks; + if let Some(disks) = disks { + for disk in disks.iter() { + disk.apply_landlock(&mut landlock)?; + } + } + + self.rng.apply_landlock(&mut landlock)?; + + if let Some(fs_configs) = &self.fs { + for fs_config in fs_configs.iter() { + fs_config.apply_landlock(&mut landlock)?; + } + } + + if let Some(pmem_configs) = &self.pmem { + for pmem_config in pmem_configs.iter() { + pmem_config.apply_landlock(&mut landlock)?; + } + } + + self.console.apply_landlock(&mut landlock)?; + self.serial.apply_landlock(&mut landlock)?; + + #[cfg(target_arch = "x86_64")] + { + self.debug_console.apply_landlock(&mut landlock)?; + } + + if let Some(devices) = &self.devices { + landlock.add_rule_with_access("/dev/vfio/vfio".into(), "rw")?; + + for device in devices.iter() { + device.apply_landlock(&mut landlock)?; + } + } + + if let Some(user_devices) = &self.user_devices { + for user_devices in user_devices.iter() { + user_devices.apply_landlock(&mut landlock)?; + } + } + + if let Some(vdpa_configs) = &self.vdpa { + for vdpa_config in vdpa_configs.iter() { + vdpa_config.apply_landlock(&mut landlock)?; + } + } + + if let Some(vsock_config) = &self.vsock { + vsock_config.apply_landlock(&mut landlock)?; + } + + if let Some(payload) = &self.payload { + payload.apply_landlock(&mut landlock)?; + } + + if let Some(tpm_config) = &self.tpm { + tpm_config.apply_landlock(&mut landlock)?; + } + + if self.net.is_some() { + landlock.add_rule_with_access("/dev/net/tun".into(), "rw")?; + } + + if let Some(landlock_configs) = &self.landlock_config { + for landlock_config in landlock_configs.iter() { + landlock_config.apply_landlock(&mut landlock)?; + } + } + + landlock.restrict_self()?; + + Ok(()) + } }