Compare commits

...

18 Commits

Author SHA1 Message Date
Praveen K Paladugu 810906d859
Merge 5cb47f6434 into d0cfa3a014 2024-04-05 22:24:35 -07:00
dependabot[bot] d0cfa3a014 build: Bump clap from 4.5.3 to 4.5.4 in /fuzz
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.3 to 4.5.4.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.3...v4.5.4)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: indirect
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-06 00:30:44 +00:00
Praveen K Paladugu 5cb47f6434 vmm: Move pty creation to vm_create
Pty devices are created during vm_boot. To enable Landlock in VMs with
pty devices, the devices have to be created during vm_create itself.
This commit moves creation of pty devices to vm_create and saves the
device info in relevant Configs.

During vm_boot, device_manager retrieves the saved device info and
uses them as required. With this change Landlock works in VMs with pty
devices enabled.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:49:24 +00:00
Praveen K Paladugu cca808865d tests: Enable landlock in test_net_hotplug
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu bc545f3b64 tests: Enable landlock in test_iommu_segments
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 69a4cda8ec tests: Enable landlock in test_disk_hotplug
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 5021aa05e8 tests: Enable landlock in test_console_file
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu ab0f103dac tests: Enable landlock in pmem_hotplug test
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 57c9124379 vmm: Enable Landlock in restore path
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 885e69e7d1 vmm: Enable Landlock on vmm thread
Add file/dir paths from landlock-rules arguments to ruleset. Invoke
apply_landlock on VmConfig to apply config specific rules to ruleset.

Once done, any threads spawned by vmm thread will be automatically
sandboxed with the ruleset in vmm thread.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 7c51498ebf vmm: Introduce apply_landlock to vm_config objects
vm_config structs with PathBuf elements now have apply_landlock method.
These methods will be used to add config specific rules to landlock
ruleset.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu f19c4922bf vmm: Enable Landlock on http-server thread
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 74632493d1 vmm: Enable Landlock on signal-handler thread
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 1a5919c94a vmm: Enable Landlock on event-monitor thread
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 22:20:28 +00:00
Praveen K Paladugu 80c629d7e9 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>
2024-04-04 22:20:27 +00:00
Praveen K Paladugu f938cd12d6 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.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 19:08:15 +00:00
Praveen K Paladugu 1ca9891b86 vmm: Introduce landlock cmdline parameter
Users can use this cmdline option to enable/disable Landlock LSM while
starting cloud-hypervisor.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 19:08:15 +00:00
Praveen K Paladugu 766cb9ba50 vmm: Add seccomp rules to allow landlock syscalls
landlock syscalls are required by event_monitor, signal_handler,
http-server and vmm threads. Rest of the threads are spawned by the vmm
thread and they automatically inherit the ruleset from the vmm thread.

Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
2024-04-04 19:08:15 +00:00
15 changed files with 798 additions and 36 deletions

15
Cargo.lock generated
View File

@ -352,6 +352,9 @@ name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
dependencies = [
"serde",
]
[[package]]
name = "block"
@ -1204,6 +1207,17 @@ dependencies = [
"vmm-sys-util",
]
[[package]]
name = "landlock"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9baa9eeb6e315942429397e617a190f4fdc696ef1ee0342939d641029cbb4ea7"
dependencies = [
"enumflags2",
"libc",
"thiserror",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -2637,6 +2651,7 @@ dependencies = [
"hypervisor",
"igvm",
"igvm_defs",
"landlock",
"libc",
"linux-loader",
"log",

4
fuzz/Cargo.lock generated
View File

@ -184,9 +184,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.3"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
]

View File

@ -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,

View File

@ -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}"),
}
}
}

View File

@ -259,6 +259,24 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String
.num_args(1..)
.group("vm-config"),
)
.arg(
Arg::new("landlock")
.long("landlock")
.num_args(0)
.help(
"eanble/disable landlock.",
)
.action(ArgAction::SetTrue)
.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")
@ -615,6 +633,8 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
let vm_debug_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateDebugEventFd)?;
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::CreateExitEventFd)?;
// TODO: landlock_enable is being parsed in 2 places: here and VMparams:parse() how to merge both?
let landlock_enable = cmd_arguments.get_flag("landlock");
#[allow(unused_mut)]
let mut event_monitor = cmd_arguments
@ -684,6 +704,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
vmm::start_event_monitor_thread(
monitor,
&seccomp_action,
landlock_enable,
hypervisor.hypervisor_type(),
exit_evt.try_clone().unwrap(),
)
@ -710,6 +731,7 @@ fn start_vmm(cmd_arguments: ArgMatches) -> Result<Option<String>, Error> {
exit_evt.try_clone().unwrap(),
&seccomp_action,
hypervisor,
landlock_enable,
)
.map_err(Error::StartVmmThread)?;
@ -913,12 +935,16 @@ mod unit_tests {
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
console: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
#[cfg(target_arch = "x86_64")]
debug_console: DebugConsoleConfig::default(),
@ -937,6 +963,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);

View File

@ -2813,6 +2813,10 @@ mod common_parallel {
"--platform",
&format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS},iommu_segments=[1]"),
])
.args([
"--landlock-rules",
format!("path={:?},flags=rw", test_disk_path).as_str(),
])
.default_disks()
.capture_output()
.default_net();
@ -4332,6 +4336,10 @@ mod common_parallel {
.args(["--memory", "size=512M"])
.args(["--kernel", direct_kernel_boot_path().to_str().unwrap()])
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
.args([
"--landlock-rules",
format!("path={:?},flags=rw", console_path.to_str().unwrap()).as_str(),
])
.default_disks()
.default_net()
.args([
@ -5157,6 +5165,10 @@ mod common_parallel {
let kernel_path = edk2_path();
let api_socket = temp_api_path(&guest.tmp_dir);
//Hotplugged disk path
let mut blk_file_path = dirs::home_dir().unwrap();
blk_file_path.push("workloads");
blk_file_path.push("blk.img");
let mut child = GuestCommand::new(&guest)
.args(["--api-socket", &api_socket])
@ -5164,6 +5176,14 @@ mod common_parallel {
.args(["--memory", "size=512M"])
.args(["--kernel", kernel_path.to_str().unwrap()])
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
.args([
"--landlock-rules",
format!(
"path={:?},flags=rw",
blk_file_path.as_path().to_str().unwrap()
)
.as_str(),
])
.default_disks()
.default_net()
.capture_output()
@ -5184,10 +5204,6 @@ mod common_parallel {
0
);
// Now let's add the extra disk.
let mut blk_file_path = dirs::home_dir().unwrap();
blk_file_path.push("workloads");
blk_file_path.push("blk.img");
let (cmd_success, cmd_output) = remote_command_w_output(
&api_socket,
"add-disk",
@ -5671,11 +5687,22 @@ mod common_parallel {
let mut cmd = GuestCommand::new(&guest);
let pmem_temp_file = TempFile::new().unwrap();
pmem_temp_file.as_file().set_len(128 << 20).unwrap();
cmd.args(["--api-socket", &api_socket])
.args(["--cpus", "boot=1"])
.args(["--memory", "size=512M"])
.args(["--kernel", kernel_path.to_str().unwrap()])
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
.args([
"--landlock-rules",
format!(
"path={:?},flags=rw",
pmem_temp_file.as_path().to_str().unwrap()
)
.as_str(),
])
.default_disks()
.default_net()
.capture_output();
@ -5703,8 +5730,6 @@ mod common_parallel {
0
);
let pmem_temp_file = TempFile::new().unwrap();
pmem_temp_file.as_file().set_len(128 << 20).unwrap();
let (cmd_success, cmd_output) = remote_command_w_output(
&api_socket,
"add-pmem",
@ -5816,6 +5841,10 @@ mod common_parallel {
.args(["--memory", "size=512M"])
.args(["--kernel", kernel_path.to_str().unwrap()])
.args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE])
.args([
"--landlock-rules",
format!("path={:?},flags=rw", "/dev/net/tun").as_str(),
])
.default_disks()
.capture_output();

View File

@ -21,7 +21,7 @@ acpi_tables = { git = "https://github.com/rust-vmm/acpi_tables", branch = "main"
anyhow = "1.0.79"
arc-swap = "1.5.1"
arch = { path = "../arch" }
bitflags = "2.4.2"
bitflags = { version = "2.4.2", features = ["serde"] }
block = { path = "../block" }
blocking = { version = "1.5.1", optional = true }
cfg-if = "1.0.0"
@ -37,6 +37,7 @@ hex = { version = "0.4.3", optional = true }
hypervisor = { path = "../hypervisor" }
igvm_defs = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm_defs", optional = true }
igvm_parser = { git = "https://github.com/microsoft/igvm", branch = "main", package = "igvm", optional = true }
landlock = "0.3.1"
libc = "0.2.153"
linux-loader = { version = "0.11.0", features = ["elf", "bzimage", "pe"] }
log = "0.4.20"

View File

@ -12,6 +12,7 @@ use crate::api::{
VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
VmSendMigration, VmShutdown, VmSnapshot,
};
use crate::landlock::Landlock;
use crate::seccomp_filters::{get_seccomp_filter, Thread};
use crate::{Error as VmmError, Result};
use core::fmt;
@ -303,6 +304,7 @@ fn start_http_thread(
seccomp_action: &SeccompAction,
exit_evt: EventFd,
hypervisor_type: HypervisorType,
landlock_enable: bool,
) -> Result<HttpApiHandle> {
// Retrieve seccomp filter for API thread
let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type)
@ -329,6 +331,18 @@ fn start_http_thread(
})?;
}
if landlock_enable {
Landlock::new()
.map_err(VmmError::ApplyLandlock)?
.restrict_self()
.map_err(VmmError::ApplyLandlock)
.map_err(|e| {
error!("Error applying landlock to http-server thread: {:?}", e);
exit_evt.write(1).ok();
e
})?;
}
std::panic::catch_unwind(AssertUnwindSafe(move || {
server.start_server().unwrap();
loop {
@ -375,6 +389,7 @@ pub fn start_http_path_thread(
seccomp_action: &SeccompAction,
exit_evt: EventFd,
hypervisor_type: HypervisorType,
landlock_enable: bool,
) -> Result<HttpApiHandle> {
let socket_path = PathBuf::from(path);
let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?;
@ -389,6 +404,7 @@ pub fn start_http_path_thread(
seccomp_action,
exit_evt,
hypervisor_type,
landlock_enable,
)
}
@ -399,6 +415,7 @@ pub fn start_http_fd_thread(
seccomp_action: &SeccompAction,
exit_evt: EventFd,
hypervisor_type: HypervisorType,
landlock_enable: bool,
) -> Result<HttpApiHandle> {
// SAFETY: Valid FD
let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?;
@ -409,6 +426,7 @@ pub fn start_http_fd_thread(
seccomp_action,
exit_evt,
hypervisor_type,
landlock_enable,
)
}

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
//
use crate::landlock;
pub use crate::vm_config::*;
use clap::ArgMatches;
use option_parser::{
@ -108,6 +109,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)]
@ -197,6 +202,8 @@ pub enum ValidationError {
InvalidIoPortHex(String),
#[cfg(feature = "sev_snp")]
InvalidHostData,
/// Path provided in landlock-rules doesn't exist
LandlockPathDoesNotExist(PathBuf),
}
type ValidationResult<T> = std::result::Result<T, ValidationError>;
@ -336,6 +343,13 @@ impl fmt::Display for ValidationError {
InvalidHostData => {
write!(f, "Invalid host data format")
}
LandlockPathDoesNotExist(s) => {
write!(
f,
"Path {:?} provided in landlock-rules does not exist",
s.as_path()
)
}
}
}
}
@ -400,6 +414,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/flags field missing"
),
}
}
}
@ -450,6 +469,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<Vec<&'a str>>,
}
impl<'a> VmParams<'a> {
@ -512,6 +533,11 @@ impl<'a> VmParams<'a> {
let igvm = args.get_one::<String>("igvm").map(|x| x as &str);
#[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,
@ -548,6 +574,8 @@ impl<'a> VmParams<'a> {
igvm,
#[cfg(feature = "sev_snp")]
host_data,
landlock_enable,
landlock_config,
}
}
}
@ -1693,6 +1721,8 @@ impl ConsoleConfig {
mode,
iommu,
socket,
pty_main: None,
pty_sub: None,
})
}
}
@ -1746,7 +1776,13 @@ impl DebugConsoleConfig {
}
}
Ok(Self { file, mode, iobase })
Ok(Self {
file,
mode,
iobase,
pty_main: None,
pty_sub: None,
})
}
}
@ -2110,6 +2146,56 @@ impl TpmConfig {
}
}
impl LandlockConfig {
pub const SYNTAX: &'static str = "Landlock parameters \
\"path=<path/to/{file/dir}>,flags={r,w,x}\". Both path and flags options are required.\"";
pub fn parse(landlock_rule: &str) -> Result<Self> {
let mut parser = OptionParser::new();
parser.add("path").add("flags");
parser
.parse(landlock_rule)
.map_err(Error::ParseLandlockRules)?;
let path = parser
.get("path")
.map(PathBuf::from)
.ok_or(Error::ParseLandlockMissingFields)?;
let mut path_flags = landlock::Perms::empty();
if let Some(flags) = parser.get("flags") {
if flags.chars().count() > 3 {
return Err(Error::ParseLandlockRules(OptionParserError::InvalidValue(
flags.to_string(),
)));
}
for c in flags.chars() {
match c {
'r' => path_flags |= landlock::Perms::READ,
'w' => path_flags |= landlock::Perms::WRITE,
'x' => path_flags |= landlock::Perms::EXECUTE,
_ => {
return Err(Error::ParseLandlockRules(OptionParserError::UnknownOption(
c.to_string(),
)))
}
}
}
}
Ok(LandlockConfig {
path,
flags: path_flags,
})
}
pub fn validate(&self, _vm_config: &VmConfig) -> ValidationResult<()> {
if !self.path.exists() {
return Err(ValidationError::LandlockPathDoesNotExist(self.path.clone()));
}
Ok(())
}
}
impl VmConfig {
fn validate_identifier(
id_list: &mut BTreeSet<String>,
@ -2616,6 +2702,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)?,
@ -2646,6 +2742,8 @@ impl VmConfig {
platform,
tpm,
preserved_fds: None,
landlock_enable: vm_params.landlock_enable,
landlock_config,
};
config.validate().map_err(Error::Validation)?;
Ok(config)
@ -2771,6 +2869,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
}
}
@ -3268,6 +3367,8 @@ mod tests {
iommu: false,
file: None,
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3277,6 +3378,8 @@ mod tests {
iommu: false,
file: None,
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3286,6 +3389,8 @@ mod tests {
iommu: false,
file: None,
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3295,6 +3400,8 @@ mod tests {
iommu: false,
file: None,
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3304,6 +3411,8 @@ mod tests {
iommu: false,
file: Some(PathBuf::from("/tmp/console")),
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3313,6 +3422,8 @@ mod tests {
iommu: true,
file: None,
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3322,6 +3433,8 @@ mod tests {
iommu: true,
file: Some(PathBuf::from("/tmp/console")),
socket: None,
pty_main: None,
pty_sub: None,
}
);
assert_eq!(
@ -3331,6 +3444,8 @@ mod tests {
iommu: true,
file: None,
socket: Some(PathBuf::from("/tmp/serial.sock")),
pty_main: None,
pty_sub: None,
}
);
Ok(())
@ -3515,12 +3630,16 @@ mod tests {
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
console: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
#[cfg(target_arch = "x86_64")]
debug_console: DebugConsoleConfig::default(),
@ -3539,6 +3658,8 @@ mod tests {
platform: None,
tpm: None,
preserved_fds: None,
landlock_enable: false,
landlock_config: None,
};
assert!(valid_config.validate().is_ok());
@ -4074,4 +4195,24 @@ 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());
assert_eq!(
LandlockConfig::parse("path=/dir/path1,flags=rw")?,
LandlockConfig {
path: PathBuf::from("/dir/path1"),
flags: landlock::Perms::READ | landlock::Perms::WRITE,
}
);
assert_eq!(
LandlockConfig::parse("path=/dir/path2,flags=x")?,
LandlockConfig {
path: PathBuf::from("/dir/path2"),
flags: landlock::Perms::EXECUTE,
}
);
Ok(())
}
}

View File

@ -493,6 +493,9 @@ pub enum DeviceManagerError {
/// Cannot create a RateLimiterGroup
RateLimiterGroupCreate(rate_limiter::group::Error),
// Missing Pty device info
MissingPtyDeviceInfo,
}
pub type DeviceManagerResult<T> = result::Result<T, DeviceManagerError>;
@ -2059,16 +2062,24 @@ impl DeviceManager {
self.console_resize_pipe = resize_pipe.map(Arc::new);
Endpoint::PtyPair(file.try_clone().unwrap(), file)
} else {
let (main, sub, path) =
create_pty().map_err(DeviceManagerError::ConsolePtyOpen)?;
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.config.lock().unwrap().console.file = Some(path.clone());
let file = main.try_clone().unwrap();
assert!(resize_pipe.is_none());
self.listen_for_sigwinch_on_tty(sub).unwrap();
self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path })));
Endpoint::PtyPair(file.try_clone().unwrap(), file)
/* In non-reboot cases pty should be configured in vm_create/vm_restore */
if console_config.pty_main.is_none()
|| console_config.pty_sub.is_none()
|| console_config.file.is_none()
{
return Err(DeviceManagerError::MissingPtyDeviceInfo);
} else {
let main = unsafe { File::from_raw_fd(console_config.pty_main.unwrap()) };
let sub = unsafe { File::from_raw_fd(console_config.pty_sub.unwrap()) };
let path = console_config.file.unwrap().clone();
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
let file = main.try_clone().unwrap();
assert!(resize_pipe.is_none());
self.listen_for_sigwinch_on_tty(sub).unwrap();
self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path })));
Endpoint::PtyPair(file.try_clone().unwrap(), file)
}
}
}
ConsoleOutputMode::Tty => {
@ -2184,12 +2195,20 @@ impl DeviceManager {
self.config.lock().unwrap().serial.file = Some(pty.path.clone());
self.serial_pty = Some(Arc::new(Mutex::new(pty)));
} else {
let (main, sub, path) =
create_pty().map_err(DeviceManagerError::SerialPtyOpen)?;
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.config.lock().unwrap().serial.file = Some(path.clone());
self.serial_pty = Some(Arc::new(Mutex::new(PtyPair { main, path })));
/* In non-reboot cases pty should be configured in vm_create/vm_restore */
if serial_config.pty_main.is_none()
|| serial_config.pty_sub.is_none()
|| serial_config.file.is_none()
{
return Err(DeviceManagerError::MissingPtyDeviceInfo);
} else {
let main = unsafe { File::from_raw_fd(serial_config.pty_main.unwrap()) };
let sub = unsafe { File::from_raw_fd(serial_config.pty_sub.unwrap()) };
let path = serial_config.file.unwrap().clone();
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.serial_pty = Some(Arc::new(Mutex::new(PtyPair { main, path })));
}
}
None
}
@ -2243,12 +2262,24 @@ impl DeviceManager {
self.config.lock().unwrap().debug_console.file = Some(pty.path.clone());
self.debug_console_pty = Some(Arc::new(Mutex::new(pty)));
} else {
let (main, sub, path) =
create_pty().map_err(DeviceManagerError::DebugconPtyOpen)?;
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.config.lock().unwrap().debug_console.file = Some(path.clone());
self.debug_console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path })));
/* In non-reboot cases pty should be configured in vm_create/vm_restore */
if debug_console_config.pty_main.is_none()
|| debug_console_config.pty_sub.is_none()
|| debug_console_config.file.is_none()
{
return Err(DeviceManagerError::MissingPtyDeviceInfo);
} else {
let main = unsafe {
File::from_raw_fd(debug_console_config.pty_main.unwrap())
};
let sub =
unsafe { File::from_raw_fd(debug_console_config.pty_sub.unwrap()) };
let path = debug_console_config.file.unwrap().clone();
self.set_raw_mode(&sub)
.map_err(DeviceManagerError::SetPtyRaw)?;
self.debug_console_pty =
Some(Arc::new(Mutex::new(PtyPair { main, path })));
}
}
None
}

113
vmm/src/landlock.rs Normal file
View File

@ -0,0 +1,113 @@
// Copyright © 2024 Microsoft Corporation
//
// SPDX-License-Identifier: Apache-2.0
use crate::{Deserialize, Serialize};
use bitflags::bitflags;
use thiserror::Error;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Perms: u8 {
const READ = 0b00000001;
const WRITE = 0b00000010;
const EXECUTE = 0b00000100;
}
}
use crate::vm_config::LandlockConfig;
use landlock::{
path_beneath_rules, Access, AccessFs, BitFlags, Ruleset, RulesetAttr, RulesetCreated,
RulesetCreatedAttr, RulesetError, ABI,
};
use std::io::Error as IoError;
use std::path::PathBuf;
#[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 Device Path
#[error("Error opening path: {0}")]
OpenPath(#[source] IoError),
/// Invalid Path
#[error("Invalid path")]
InvalidPath,
#[error("Error creating ptys")]
CreatePtysFailed(#[source] std::io::Error),
}
static ABI: ABI = ABI::V3;
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)?;
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(())
}
fn flags_to_access(&self, flags: Perms) -> BitFlags<AccessFs> {
let mut perms = BitFlags::<AccessFs>::empty();
if flags & Perms::READ != Perms::empty() {
perms |= AccessFs::from_read(ABI);
}
if flags & Perms::READ != Perms::empty() {
perms |= AccessFs::from_write(ABI);
}
perms
}
pub fn add_rule_with_flags(
&mut self,
path: PathBuf,
flags: Perms,
) -> Result<(), LandlockError> {
self.add_rule(path.to_path_buf(), self.flags_to_access(flags))?;
Ok(())
}
pub fn apply_config(
&mut self,
landlock_config: Vec<LandlockConfig>,
) -> Result<(), LandlockError> {
for config in landlock_config {
self.add_rule(config.path, self.flags_to_access(config.flags))?;
}
Ok(())
}
pub fn restrict_self(self) -> Result<(), LandlockError> {
self.ruleset
.restrict_self()
.map_err(LandlockError::ManageRuleset)?;
Ok(())
}
}

View File

@ -18,6 +18,7 @@ use crate::config::{
};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
use crate::coredump::GuestDebuggable;
use crate::landlock::Landlock;
use crate::memory_manager::MemoryManager;
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
use crate::migration::get_vm_snapshot;
@ -28,6 +29,8 @@ use anyhow::anyhow;
#[cfg(feature = "dbus_api")]
use api::dbus::{DBusApiOptions, DBusApiShutdownChannels};
use api::http::HttpApiHandle;
use device_manager::create_pty;
use landlock::LandlockError;
use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW};
use memory_manager::MemoryManagerSnapshotData;
use pci::PciBdf;
@ -39,6 +42,7 @@ use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::{stdout, Read, Write};
use std::os::fd::IntoRawFd;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixListener;
use std::os::unix::net::UnixStream;
@ -51,6 +55,7 @@ use std::time::Instant;
use std::{result, thread};
use thiserror::Error;
use tracer::trace_scoped;
use vm_config::ConsoleOutputMode;
use vm_memory::bitmap::AtomicBitmap;
use vm_memory::{ReadVolatile, WriteVolatile};
use vm_migration::{protocol::*, Migratable};
@ -73,6 +78,7 @@ mod gdb;
#[cfg(feature = "igvm")]
mod igvm;
pub mod interrupt;
pub mod landlock;
pub mod memory_manager;
pub mod migration;
mod pci_segment;
@ -193,6 +199,10 @@ pub enum Error {
#[error("Failed to join on threads: {0:?}")]
ThreadCleanup(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
/// Cannot apply landlock LSM
#[error("Error applying Landlock LSM: {0}")]
ApplyLandlock(LandlockError),
}
pub type Result<T> = result::Result<T, Error>;
@ -325,6 +335,7 @@ pub fn feature_list() -> Vec<String> {
pub fn start_event_monitor_thread(
mut monitor: event_monitor::Monitor,
seccomp_action: &SeccompAction,
landlock_enable: bool,
hypervisor_type: hypervisor::HypervisorType,
exit_event: EventFd,
) -> Result<thread::JoinHandle<Result<()>>> {
@ -345,6 +356,17 @@ pub fn start_event_monitor_thread(
e
})?;
}
if landlock_enable {
Landlock::new()
.map_err(Error::ApplyLandlock)?
.restrict_self()
.map_err(Error::ApplyLandlock)
.map_err(|e| {
error!("Error applying landlock to event monitor thread: {:?}", e);
exit_event.write(1).ok();
e
})?;
}
std::panic::catch_unwind(AssertUnwindSafe(move || {
while let Ok(event) = monitor.rx.recv() {
@ -387,6 +409,7 @@ pub fn start_vmm_thread(
exit_event: EventFd,
seccomp_action: &SeccompAction,
hypervisor: Arc<dyn hypervisor::Hypervisor>,
landlock_enable: bool,
) -> Result<VmmThreadHandle> {
#[cfg(feature = "guest_debug")]
let gdb_hw_breakpoints = hypervisor.get_guest_debug_hw_bps();
@ -427,7 +450,7 @@ pub fn start_vmm_thread(
exit_event,
)?;
vmm.setup_signal_handler()?;
vmm.setup_signal_handler(landlock_enable)?;
vmm.control_loop(
Rc::new(api_receiver),
@ -464,6 +487,7 @@ pub fn start_vmm_thread(
seccomp_action,
exit_event,
hypervisor_type,
landlock_enable,
)?)
} else if let Some(http_fd) = http_fd {
Some(api::start_http_fd_thread(
@ -473,6 +497,7 @@ pub fn start_vmm_thread(
seccomp_action,
exit_event,
hypervisor_type,
landlock_enable,
)?)
} else {
None
@ -586,7 +611,7 @@ impl Vmm {
}
}
fn setup_signal_handler(&mut self) -> Result<()> {
fn setup_signal_handler(&mut self, landlock_enable: bool) -> Result<()> {
let signals = Signals::new(Self::HANDLED_SIGNALS);
match signals {
Ok(signals) => {
@ -613,6 +638,21 @@ impl Vmm {
return;
}
}
if landlock_enable{
match Landlock::new() {
Ok(landlock) => {
let _ = landlock.restrict_self().map_err(Error::ApplyLandlock).map_err(|e| {
error!("Error applying Landlock to signal handler thread: {:?}", e);
exit_evt.write(1).ok();
});
}
Err(e) => {
error!("Error creating Landlock instance: {:?}", e);
exit_evt.write(1).ok();
}
};
}
std::panic::catch_unwind(AssertUnwindSafe(|| {
Vmm::signal_handler(signals, original_termios_opt, &exit_evt);
}))
@ -630,6 +670,7 @@ impl Vmm {
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn new(
vmm_version: VmmVersionInfo,
api_evt: EventFd,
@ -1202,12 +1243,58 @@ impl Vmm {
}
}
/* Create ptys here and add the slave paths to Serial, Console and DebugConsole
configs. This allows apply_landlok to add the slave paths to landlock rules
later.
*/
fn create_ptys(config: Arc<Mutex<VmConfig>>) -> result::Result<(), LandlockError> {
if config.lock().unwrap().console.mode == ConsoleOutputMode::Pty {
let (main_fd, sub_fd, path) = create_pty().map_err(LandlockError::CreatePtysFailed)?;
config.lock().unwrap().console.pty_main = Some(main_fd.into_raw_fd());
config.lock().unwrap().console.pty_sub = Some(sub_fd.into_raw_fd());
config.lock().unwrap().console.file = Some(path);
}
if config.lock().unwrap().serial.mode == ConsoleOutputMode::Pty {
let (main_fd, sub_fd, path) = create_pty().map_err(LandlockError::CreatePtysFailed)?;
config.lock().unwrap().serial.pty_main = Some(main_fd.into_raw_fd());
config.lock().unwrap().serial.pty_sub = Some(sub_fd.into_raw_fd());
config.lock().unwrap().serial.file = Some(path);
}
#[cfg(target_arch = "x86_64")]
if config.lock().unwrap().debug_console.mode == ConsoleOutputMode::Pty {
let (main_fd, sub_fd, path) = create_pty().map_err(LandlockError::CreatePtysFailed)?;
config.lock().unwrap().debug_console.pty_main = Some(main_fd.into_raw_fd());
config.lock().unwrap().debug_console.pty_sub = Some(sub_fd.into_raw_fd());
config.lock().unwrap().debug_console.file = Some(path);
}
Ok(())
}
fn apply_landlock(vm_config: Arc<Mutex<VmConfig>>) -> result::Result<(), LandlockError> {
create_ptys(vm_config.clone())?;
vm_config.lock().unwrap().apply_landlock()?;
Ok(())
}
impl RequestHandler for Vmm {
fn vm_create(&mut self, config: Arc<Mutex<VmConfig>>) -> result::Result<(), VmError> {
// We only store the passed VM config.
// The VM will be created when being asked to boot it.
if self.vm_config.is_none() {
self.vm_config = Some(config);
if self
.vm_config
.as_ref()
.unwrap()
.lock()
.unwrap()
.landlock_enable
{
apply_landlock(self.vm_config.as_mut().unwrap().clone())
.map_err(VmError::ApplyLandlock)?;
}
Ok(())
} else {
Err(VmError::VmAlreadyCreated)
@ -1363,6 +1450,18 @@ impl RequestHandler for Vmm {
)?;
self.vm = Some(vm);
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.
if let Some(ref mut vm) = self.vm {
vm.restore()
@ -2130,12 +2229,16 @@ mod unit_tests {
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
console: ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
},
#[cfg(target_arch = "x86_64")]
debug_console: DebugConsoleConfig::default(),
@ -2154,6 +2257,8 @@ mod unit_tests {
platform: None,
tpm: None,
preserved_fds: None,
landlock_enable: false,
landlock_config: None,
}))
}

View File

@ -495,9 +495,12 @@ fn signal_handler_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, Backend
(libc::SYS_exit_group, vec![]),
(libc::SYS_futex, vec![]),
(libc::SYS_ioctl, create_signal_handler_ioctl_seccomp_rule()?),
(libc::SYS_landlock_create_ruleset, vec![]),
(libc::SYS_landlock_restrict_self, vec![]),
(libc::SYS_madvise, vec![]),
(libc::SYS_mmap, vec![]),
(libc::SYS_munmap, vec![]),
(libc::SYS_prctl, vec![]),
(libc::SYS_recvfrom, vec![]),
(libc::SYS_rt_sigprocmask, vec![]),
(libc::SYS_rt_sigreturn, vec![]),
@ -600,6 +603,9 @@ fn vmm_thread_rules(
(libc::SYS_io_uring_setup, vec![]),
(libc::SYS_io_uring_register, vec![]),
(libc::SYS_kill, vec![]),
(libc::SYS_landlock_create_ruleset, vec![]),
(libc::SYS_landlock_add_rule, vec![]),
(libc::SYS_landlock_restrict_self, vec![]),
(libc::SYS_listen, vec![]),
(libc::SYS_lseek, vec![]),
(libc::SYS_madvise, vec![]),
@ -838,10 +844,13 @@ fn http_api_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError>
(libc::SYS_futex, vec![]),
(libc::SYS_getrandom, vec![]),
(libc::SYS_ioctl, create_api_ioctl_seccomp_rule()?),
(libc::SYS_landlock_create_ruleset, vec![]),
(libc::SYS_landlock_restrict_self, vec![]),
(libc::SYS_madvise, vec![]),
(libc::SYS_mmap, vec![]),
(libc::SYS_mprotect, vec![]),
(libc::SYS_munmap, vec![]),
(libc::SYS_prctl, vec![]),
(libc::SYS_recvfrom, vec![]),
(libc::SYS_recvmsg, vec![]),
(libc::SYS_sched_yield, vec![]),
@ -891,9 +900,13 @@ fn dbus_api_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError>
fn event_monitor_thread_rules() -> Result<Vec<(i64, Vec<SeccompRule>)>, BackendError> {
Ok(vec![
(libc::SYS_brk, vec![]),
(libc::SYS_close, vec![]),
(libc::SYS_futex, vec![]),
(libc::SYS_landlock_create_ruleset, vec![]),
(libc::SYS_landlock_restrict_self, vec![]),
(libc::SYS_mmap, vec![]),
(libc::SYS_munmap, vec![]),
(libc::SYS_prctl, vec![]),
(libc::SYS_sched_yield, vec![]),
(libc::SYS_write, vec![]),
])

View File

@ -27,6 +27,7 @@ use crate::device_tree::DeviceTree;
use crate::gdb::{Debuggable, DebuggableError, GdbRequestPayload, GdbResponsePayload};
#[cfg(feature = "igvm")]
use crate::igvm::igvm_loader;
use crate::landlock::LandlockError;
use crate::memory_manager::{
Error as MemoryManagerError, MemoryManager, MemoryManagerSnapshotData,
};
@ -121,6 +122,9 @@ pub enum Error {
#[error("Cannot load the kernel command line in memory: {0}")]
LoadCmdLine(#[source] linux_loader::loader::Error),
#[error("Failed to apply landlock config during vm_create: {0}")]
ApplyLandlock(#[source] LandlockError),
#[error("Cannot modify the kernel command line: {0}")]
CmdLineInsertStr(#[source] linux_loader::cmdline::Error),

View File

@ -2,11 +2,17 @@
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::{
landlock::{self, LandlockError},
Landlock,
};
use net_util::MacAddr;
use serde::{Deserialize, Serialize};
use std::{net::Ipv4Addr, path::PathBuf};
use std::{fs, net::Ipv4Addr, os::fd::RawFd, path::PathBuf, result};
use virtio_devices::RateLimiterConfig;
pub type LandlockResult<T> = result::Result<T, LandlockError>;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct CpuAffinity {
pub vcpu: u8,
@ -115,7 +121,15 @@ pub struct MemoryZoneConfig {
#[serde(default)]
pub prefault: bool,
}
impl MemoryZoneConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let memory_zone_flags = landlock::Perms::READ | landlock::Perms::WRITE;
if let Some(file) = &self.file {
landlock.add_rule_with_flags(file.to_path_buf(), memory_zone_flags)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, Default)]
pub enum HotplugMethod {
#[default]
@ -229,6 +243,16 @@ pub struct DiskConfig {
pub queue_affinity: Option<Vec<VirtQueueAffinity>>,
}
impl DiskConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
// Allow Read and Write permissions to Disk Paths
let disk_flags = landlock::Perms::READ | landlock::Perms::WRITE;
if let Some(path) = self.path {
landlock.add_rule_with_flags(path, disk_flags)?;
}
Ok(())
}
}
pub const DEFAULT_DISK_NUM_QUEUES: usize = 1;
pub fn default_diskconfig_num_queues() -> usize {
@ -362,6 +386,15 @@ impl Default for RngConfig {
}
}
impl RngConfig {
pub fn apply_landlock(&mut self, landlock: &mut Landlock) -> LandlockResult<()> {
// Allow Read permissions to Rng Paths
let rng_flags = landlock::Perms::READ;
landlock.add_rule_with_flags(self.src.to_path_buf(), rng_flags)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BalloonConfig {
pub size: u64,
@ -395,6 +428,14 @@ pub fn default_fsconfig_queue_size() -> u16 {
1024
}
impl FsConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let fs_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.socket, fs_flags)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct PmemConfig {
pub file: PathBuf,
@ -410,6 +451,15 @@ pub struct PmemConfig {
pub pci_segment: u16,
}
impl PmemConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
// Allow Read and Write permissions to Pmem Paths
let pmem_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.file, pmem_flags)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum ConsoleOutputMode {
Off,
@ -428,12 +478,29 @@ pub struct ConsoleConfig {
#[serde(default)]
pub iommu: bool,
pub socket: Option<PathBuf>,
#[serde(skip)]
pub pty_main: Option<RawFd>,
pub pty_sub: Option<RawFd>,
}
pub fn default_consoleconfig_file() -> Option<PathBuf> {
None
}
impl ConsoleConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let console_flags = landlock::Perms::READ | landlock::Perms::WRITE;
if let Some(file) = self.file {
landlock.add_rule_with_flags(file, console_flags)?;
}
if let Some(socket) = self.socket {
landlock.add_rule_with_flags(socket, console_flags)?;
}
Ok(())
}
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct DebugConsoleConfig {
@ -442,6 +509,9 @@ pub struct DebugConsoleConfig {
pub mode: ConsoleOutputMode,
/// Optionally dedicated I/O-port, if the default port should not be used.
pub iobase: Option<u16>,
#[serde(skip)]
pub pty_main: Option<RawFd>,
pub pty_sub: Option<RawFd>,
}
#[cfg(target_arch = "x86_64")]
@ -451,9 +521,21 @@ impl Default for DebugConsoleConfig {
file: None,
mode: ConsoleOutputMode::Off,
iobase: Some(devices::debug_console::DEFAULT_PORT as u16),
pty_main: None,
pty_sub: None,
}
}
}
#[cfg(target_arch = "x86_64")]
impl DebugConsoleConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let debug_console_flags = landlock::Perms::READ | landlock::Perms::WRITE;
if let Some(file) = self.file {
landlock.add_rule_with_flags(file, debug_console_flags)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct DeviceConfig {
@ -468,6 +550,23 @@ pub struct DeviceConfig {
pub x_nv_gpudirect_clique: Option<u8>,
}
impl DeviceConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let device_flags = landlock::Perms::READ | landlock::Perms::WRITE;
let device_path = fs::read_link(self.path).map_err(LandlockError::OpenPath)?;
let iommu_group = device_path.file_name();
if let Some(iommu_group) = iommu_group {
if let Some(iommu_group_str) = iommu_group.to_str() {
let vfio_group_path = "/dev/vfio/".to_owned() + iommu_group_str;
landlock.add_rule_with_flags(vfio_group_path.into(), device_flags)?;
} else {
return Err(LandlockError::InvalidPath);
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct UserDeviceConfig {
pub socket: PathBuf,
@ -477,6 +576,14 @@ pub struct UserDeviceConfig {
pub pci_segment: u16,
}
impl UserDeviceConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let user_device_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.socket, user_device_flags)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct VdpaConfig {
pub path: PathBuf,
@ -494,6 +601,14 @@ pub fn default_vdpaconfig_num_queues() -> usize {
1
}
impl VdpaConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let vdpa_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.path, vdpa_flags)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct VsockConfig {
pub cid: u32,
@ -506,6 +621,13 @@ pub struct VsockConfig {
pub pci_segment: u16,
}
impl VsockConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let vsock_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.socket, vsock_flags)
}
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct SgxEpcConfig {
@ -559,12 +681,38 @@ pub struct PayloadConfig {
pub host_data: Option<String>,
}
impl PayloadConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let payload_flags = landlock::Perms::READ;
if let Some(firmware) = self.firmware.as_ref() {
landlock.add_rule_with_flags(firmware.to_path_buf(), payload_flags)?;
}
if let Some(kernel) = self.kernel.as_ref() {
landlock.add_rule_with_flags(kernel.to_path_buf(), payload_flags)?;
}
if let Some(initramfs) = self.initramfs.as_ref() {
landlock.add_rule_with_flags(initramfs.to_path_buf(), payload_flags)?;
}
#[cfg(feature = "igvm")]
if let Some(igvm) = self.igvm.as_ref() {
landlock.add_rule_with_flags(igvm.to_path_buf(), payload_flags)?;
}
Ok(())
}
}
pub fn default_serial() -> ConsoleConfig {
ConsoleConfig {
file: None,
mode: ConsoleOutputMode::Null,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
}
}
@ -574,6 +722,8 @@ pub fn default_console() -> ConsoleConfig {
mode: ConsoleOutputMode::Tty,
iommu: false,
socket: None,
pty_main: None,
pty_sub: None,
}
}
@ -582,6 +732,24 @@ pub struct TpmConfig {
pub socket: PathBuf,
}
impl TpmConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
let tpm_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags(self.socket, tpm_flags)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct LandlockConfig {
pub path: PathBuf,
pub flags: landlock::Perms,
}
impl LandlockConfig {
pub fn apply_landlock(self, landlock: &mut Landlock) -> LandlockResult<()> {
landlock.add_rule_with_flags(self.path, self.flags)
}
}
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct VmConfig {
#[serde(default)]
@ -628,4 +796,96 @@ pub struct VmConfig {
// valid, and will be closed when the holding VmConfig instance is destroyed.
#[serde(skip)]
pub preserved_fds: Option<Vec<i32>>,
#[serde(default)]
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.as_ref() {
for zone in mem_zones.iter() {
zone.clone().apply_landlock(&mut landlock)?;
}
}
let disks = self.disks.as_ref();
if let Some(disks) = disks {
for disk in disks.iter() {
disk.clone().apply_landlock(&mut landlock)?;
}
}
self.rng.clone().apply_landlock(&mut landlock)?;
if let Some(fs_configs) = self.fs.as_ref() {
for fs_config in fs_configs.iter() {
fs_config.clone().apply_landlock(&mut landlock)?;
}
}
if let Some(pmem_configs) = self.pmem.as_ref() {
for pmem_config in pmem_configs.iter() {
pmem_config.clone().apply_landlock(&mut landlock)?;
}
}
self.console.clone().apply_landlock(&mut landlock)?;
self.serial.clone().apply_landlock(&mut landlock)?;
#[cfg(target_arch = "x86_64")]
{
self.debug_console.clone().apply_landlock(&mut landlock)?;
}
if let Some(devices) = self.devices.as_ref() {
let vfio_dev_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags("/dev/vfio/vfio".into(), vfio_dev_flags)?;
for device in devices.iter() {
device.clone().apply_landlock(&mut landlock)?;
}
}
if let Some(user_devices) = self.user_devices.as_ref() {
for user_devices in user_devices.iter() {
user_devices.clone().apply_landlock(&mut landlock)?;
}
}
if let Some(vdpa_configs) = self.vdpa.as_ref() {
for vdpa_config in vdpa_configs.iter() {
vdpa_config.clone().apply_landlock(&mut landlock)?;
}
}
if let Some(vsock_config) = self.vsock.as_ref() {
vsock_config.clone().apply_landlock(&mut landlock)?;
}
let payload = self.payload.as_ref();
if let Some(payload) = payload {
payload.clone().apply_landlock(&mut landlock)?;
}
if let Some(tpm_config) = self.tpm.as_ref() {
tpm_config.clone().apply_landlock(&mut landlock)?;
}
if self.net.is_some() {
let net_flags = landlock::Perms::READ | landlock::Perms::WRITE;
landlock.add_rule_with_flags("/dev/net/tun".into(), net_flags)?;
}
if self.landlock_config.is_some() {
for landlock_config in self.landlock_config.as_ref().unwrap() {
landlock_config.clone().apply_landlock(&mut landlock)?;
}
}
landlock.restrict_self()?;
Ok(())
}
}