vmm: Introduce ApplyLandlock trait

Introduce ApplyLandlock trait and add implementations to VmConfig
elements with PathBufs. This trait adds config specific rules to
landlock ruleset.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
This commit is contained in:
Praveen K Paladugu 2024-02-22 20:40:45 +00:00 committed by Liu Wei
parent 1dd53c3d24
commit b3e5738b40
2 changed files with 237 additions and 1 deletions

View File

@ -26,6 +26,10 @@ pub enum LandlockError {
/// Invalid Landlock access
#[error("Invalid Landlock access: {0}")]
InvalidLandlockAccess(String),
/// Invalid Path
#[error("Invalid path")]
InvalidPath,
}
// https://docs.rs/landlock/latest/landlock/enum.ABI.html for more info on ABI

View File

@ -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<T> = result::Result<T, LandlockError>;
/// 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<Vec<VirtQueueAffinity>>,
}
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<PathBuf> {
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<u8>,
}
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<String>,
}
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,12 +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)]
@ -655,3 +800,90 @@ pub struct VmConfig {
pub landlock_enable: bool,
pub landlock_config: Option<Vec<LandlockConfig>>,
}
impl VmConfig {
pub 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(())
}
}