From ed071b6edd4344d319ecbbbd518ef74a7b8bc18d Mon Sep 17 00:00:00 2001 From: Alex Gimenez Date: Tue, 28 Jun 2022 18:28:41 +0900 Subject: [PATCH] crosvm: implement lock-guest-memory feature. This CL adds a command-line flag to crosm that locks the entire guest memory, per http://go/host-assisted-vm-swap. The change relies on existing balloon device code that punches holes within the guest memory region upon "inflate". BUG=b:236210703 TEST=manual startup, roblox+instagram and various chrome sessions Change-Id: I11e0e5b0b0cb6450f47124a6a8b6c4befd9de6ae Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3725731 Reviewed-by: Keiichi Watanabe Reviewed-by: David Stevens Tested-by: kokoro Commit-Queue: Alexandre Marciano Gimenez Auto-Submit: Alexandre Marciano Gimenez --- base/src/sys/unix/mmap.rs | 36 ++++++++++++++++++++++++++ src/crosvm/cmdline.rs | 5 ++++ src/crosvm/config.rs | 6 +++++ src/crosvm/linux/mod.rs | 4 +++ vm_memory/src/guest_memory/sys/unix.rs | 22 ++++++++++++++-- 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/base/src/sys/unix/mmap.rs b/base/src/sys/unix/mmap.rs index c434a5ccd0..146e67af98 100644 --- a/base/src/sys/unix/mmap.rs +++ b/base/src/sys/unix/mmap.rs @@ -12,6 +12,8 @@ use std::{ ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned}, }; +use log::warn; + use crate::{ external_mapping::ExternalMapping, AsRawDescriptor, Descriptor, MemoryMapping as CrateMemoryMapping, MemoryMappingBuilder, RawDescriptor, SafeDescriptor, @@ -703,6 +705,34 @@ impl MemoryMapping { } } + /// Disable host swap for this mapping. + pub fn lock_all(&self) -> Result<()> { + let ret = unsafe { + // Safe because MLOCK_ONFAULT only affects the swap behavior of the kernel, so it + // has no impact on rust semantics. + // TODO(raging): use the explicit declaration of mlock2, which was being merged + // as of when the call below was being worked on. + libc::syscall( + libc::SYS_mlock2, + (self.addr as usize) as *const libc::c_void, + self.size(), + libc::MLOCK_ONFAULT, + ) + }; + if ret < 0 { + let errno = super::Error::last(); + warn!( + "failed to mlock at {:#x} with length {}: {}", + (self.addr as usize) as u64, + self.size(), + errno, + ); + Err(Error::SystemCallFailed(errno)) + } else { + Ok(()) + } + } + // Check that offset+count is valid and return the sum. fn range_end(&self, offset: usize, count: usize) -> Result { let mem_end = offset.checked_add(count).ok_or(Error::InvalidAddress)?; @@ -990,13 +1020,19 @@ impl CrateMemoryMapping { } pub trait Unix { + /// Remove the specified range from the mapping. fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()>; + /// Disable host swap for this mapping. + fn lock_all(&self) -> Result<()>; } impl Unix for CrateMemoryMapping { fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()> { self.mapping.remove_range(mem_offset, count) } + fn lock_all(&self) -> Result<()> { + self.mapping.lock_all() + } } pub trait MemoryMappingBuilderUnix<'a> { diff --git a/src/crosvm/cmdline.rs b/src/crosvm/cmdline.rs index b25054805d..fac33608ba 100644 --- a/src/crosvm/cmdline.rs +++ b/src/crosvm/cmdline.rs @@ -651,6 +651,9 @@ pub struct RunCommand { #[argh(option, long = "kvm-device", arg_name = "PATH")] /// path to the KVM device. (default /dev/kvm) pub kvm_device_path: Option, + #[argh(switch)] + /// disable host swap on guest VM pages. + pub lock_guest_memory: bool, #[cfg(unix)] #[argh(option, arg_name = "MAC", long = "mac")] /// MAC address for VM @@ -1197,6 +1200,8 @@ impl TryFrom for super::config::Config { cfg.hugepages = cmd.hugepages; + cfg.lock_guest_memory = cmd.lock_guest_memory; + #[cfg(feature = "audio")] { cfg.ac97_parameters = cmd.ac97; diff --git a/src/crosvm/config.rs b/src/crosvm/config.rs index 724693fe0c..ad5b8bc296 100644 --- a/src/crosvm/config.rs +++ b/src/crosvm/config.rs @@ -1720,6 +1720,7 @@ pub struct Config { pub itmt: bool, pub jail_config: Option, pub kvm_device_path: PathBuf, + pub lock_guest_memory: bool, pub mac_address: Option, pub memory: Option, pub memory_file: Option, @@ -1856,6 +1857,7 @@ impl Default for Config { None }, kvm_device_path: PathBuf::from(KVM_PATH), + lock_guest_memory: false, mac_address: None, memory: None, memory_file: None, @@ -2137,6 +2139,10 @@ pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> { return Err("'balloon-control' requires enabled balloon".to_string()); } + if cfg.lock_guest_memory && cfg.jail_config.is_none() { + return Err("'lock-guest-memory' and 'disable-sandbox' are mutually exclusive".to_string()); + } + set_default_serial_parameters( &mut cfg.serial_parameters, !cfg.vhost_user_console.is_empty(), diff --git a/src/crosvm/linux/mod.rs b/src/crosvm/linux/mod.rs index f8088b10f0..5e31ca82cc 100644 --- a/src/crosvm/linux/mod.rs +++ b/src/crosvm/linux/mod.rs @@ -1201,6 +1201,10 @@ pub fn run_config(cfg: Config) -> Result { if components.hugepages { mem_policy |= MemoryPolicy::USE_HUGEPAGES; } + + if cfg.lock_guest_memory { + mem_policy |= MemoryPolicy::LOCK_GUEST_MEMORY; + } guest_mem.set_memory_policy(mem_policy); if cfg.kvm_device_path.exists() { diff --git a/vm_memory/src/guest_memory/sys/unix.rs b/vm_memory/src/guest_memory/sys/unix.rs index d3b0624b12..555c9ef438 100644 --- a/vm_memory/src/guest_memory/sys/unix.rs +++ b/vm_memory/src/guest_memory/sys/unix.rs @@ -11,6 +11,7 @@ use crate::{Error, GuestAddress, GuestMemory, Result}; bitflags! { pub struct MemoryPolicy: u32 { const USE_HUGEPAGES = 1; + const LOCK_GUEST_MEMORY = (1 << 1); } } @@ -41,14 +42,31 @@ impl GuestMemory { /// Handles guest memory policy hints/advices. pub fn set_memory_policy(&self, mem_policy: MemoryPolicy) { - if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) { - for (_, region) in self.regions.iter().enumerate() { + if mem_policy.is_empty() { + return; + } + + for (_, region) in self.regions.iter().enumerate() { + if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) { let ret = region.mapping.use_hugepages(); if let Err(err) = ret { println!("Failed to enable HUGEPAGE for mapping {}", err); } } + + if mem_policy.contains(MemoryPolicy::LOCK_GUEST_MEMORY) { + // This is done in coordination with remove_range() calls, which are + // performed by the virtio-balloon process (they must be performed by + // a different process from the one that issues the locks). + // We also prevent this from happening in single-process configurations, + // when we compute configuration flags. + let ret = region.mapping.lock_all(); + + if let Err(err) = ret { + println!("Failed to lock memory for mapping {}", err); + } + } } } }