Separate virtualizationservice/manager code into own files
So as to avoid rebase issues during development, the separation between
virtmgr and virtualizationservice was done within one code base. Now
that the components are running separately at runtime, it is time to
separate them into two source folders as well.
As a first step, move code such that the two binaries do not share any
source files. These will be moved to the respective folders in a
subsequent change.
Bug: 245727626
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: Ifcd7c4e32c36a0ba64ed1a955a289d6760e2010f
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index d0dde42..c6add24 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -82,9 +82,10 @@
rust_test {
name: "virtualizationservice_device_test",
- srcs: ["src/main.rs"],
+ srcs: ["src/virtmgr.rs"],
defaults: ["virtualizationservice_defaults"],
rustlibs: [
+ "libclap",
"libtempfile",
],
test_suites: ["general-tests"],
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index bf49fef..c827c2e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,7 +16,6 @@
use crate::{get_calling_pid, get_calling_uid};
use crate::atom::{
- forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom,
write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
@@ -42,63 +41,41 @@
VirtualMachineRawConfig::VirtualMachineRawConfig,
VirtualMachineState::VirtualMachineState,
};
-use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
- AtomVmBooted::AtomVmBooted,
- AtomVmCreationRequested::AtomVmCreationRequested,
- AtomVmExited::AtomVmExited,
- IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
- IVirtualizationServiceInternal::IVirtualizationServiceInternal,
-};
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal::IVirtualizationServiceInternal;
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
- BnVirtualMachineService, IVirtualMachineService, VM_TOMBSTONES_SERVICE_PORT,
+ BnVirtualMachineService, IVirtualMachineService,
};
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
use apkverify::{HashAlgorithm, V4Signature};
use binder::{
- self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard,
- ParcelFileDescriptor, Status, StatusCode, Strong,
+ self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor,
+ Status, StatusCode, Strong,
};
use disk::QcowFile;
use lazy_static::lazy_static;
-use libc::VMADDR_CID_HOST;
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
use rpcbinder::RpcServer;
-use rustutils::system_properties;
use semver::VersionReq;
-use std::collections::HashMap;
use std::convert::TryInto;
use std::ffi::CStr;
-use std::fs::{create_dir, read_dir, remove_dir, remove_file, set_permissions, File, OpenOptions, Permissions};
-use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write};
+use std::fs::{read_dir, remove_file, File, OpenOptions};
+use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
use std::num::NonZeroU32;
-use std::os::unix::fs::PermissionsExt;
use std::os::unix::io::{FromRawFd, IntoRawFd};
-use std::os::unix::raw::{pid_t, uid_t};
+use std::os::unix::raw::pid_t;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
-use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
use vmconfig::VmConfig;
-use vsock::{VsockListener, VsockStream};
+use vsock::VsockStream;
use zip::ZipArchive;
-use nix::unistd::{chown, Uid};
/// The unique ID of a VM used (together with a port number) for vsock communication.
pub type Cid = u32;
pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
-/// Directory in which to write disk image files used while running VMs.
-pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
-
-/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
-/// are reserved for the host or other usage.
-const GUEST_CID_MIN: Cid = 2048;
-const GUEST_CID_MAX: Cid = 65535;
-
-const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
-
/// The size of zero.img.
/// Gaps in composite disk images are filled with a shared zero.img.
const ZERO_FILLER_SIZE: u64 = 4096;
@@ -109,8 +86,6 @@
/// Version of the instance image format
const ANDROID_VM_INSTANCE_VERSION: u16 = 1;
-const CHUNK_RECV_MAX_LEN: usize = 1024;
-
const MICRODROID_OS_NAME: &str = "microdroid";
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
@@ -121,10 +96,6 @@
.expect("Could not connect to VirtualizationServiceInternal");
}
-fn is_valid_guest_cid(cid: Cid) -> bool {
- (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
-}
-
fn create_or_update_idsig_file(
input_fd: &ParcelFileDescriptor,
idsig_fd: &ParcelFileDescriptor,
@@ -143,224 +114,6 @@
Ok(())
}
-/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
-/// singleton servers, like tombstone receiver.
-#[derive(Debug, Default)]
-pub struct VirtualizationServiceInternal {
- state: Arc<Mutex<GlobalState>>,
-}
-
-impl VirtualizationServiceInternal {
- // TODO(b/245727626): Remove after the source files for virtualizationservice
- // and virtmgr binaries are split from each other.
- #[allow(dead_code)]
- pub fn init() -> VirtualizationServiceInternal {
- let service = VirtualizationServiceInternal::default();
-
- std::thread::spawn(|| {
- if let Err(e) = handle_stream_connection_tombstoned() {
- warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
- }
- });
-
- service
- }
-}
-
-impl Interface for VirtualizationServiceInternal {}
-
-impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
- fn removeMemlockRlimit(&self) -> binder::Result<()> {
- let pid = get_calling_pid();
- let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
-
- // SAFETY - borrowing the new limit struct only
- let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
-
- match ret {
- 0 => Ok(()),
- -1 => Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some(std::io::Error::last_os_error().to_string()),
- )),
- n => Err(Status::new_exception_str(
- ExceptionCode::ILLEGAL_STATE,
- Some(format!("Unexpected return value from prlimit(): {n}")),
- )),
- }
- }
-
- fn allocateGlobalVmContext(
- &self,
- requester_debug_pid: i32,
- ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
- check_manage_access()?;
-
- let requester_uid = get_calling_uid();
- let requester_debug_pid = requester_debug_pid as pid_t;
- let state = &mut *self.state.lock().unwrap();
- state.allocate_vm_context(requester_uid, requester_debug_pid).map_err(|e| {
- Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
- })
- }
-
- fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
- forward_vm_booted_atom(atom);
- Ok(())
- }
-
- fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
- forward_vm_creation_atom(atom);
- Ok(())
- }
-
- fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
- forward_vm_exited_atom(atom);
- Ok(())
- }
-
- fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
- check_debug_access()?;
-
- let state = &mut *self.state.lock().unwrap();
- let cids = state
- .held_contexts
- .iter()
- .filter_map(|(_, inst)| Weak::upgrade(inst))
- .map(|vm| VirtualMachineDebugInfo {
- cid: vm.cid as i32,
- temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
- requesterUid: vm.requester_uid as i32,
- requesterPid: vm.requester_debug_pid as i32,
- })
- .collect();
- Ok(cids)
- }
-}
-
-#[derive(Debug, Default)]
-struct GlobalVmInstance {
- /// The unique CID assigned to the VM for vsock communication.
- cid: Cid,
- /// UID of the client who requested this VM instance.
- requester_uid: uid_t,
- /// PID of the client who requested this VM instance.
- requester_debug_pid: pid_t,
-}
-
-impl GlobalVmInstance {
- fn get_temp_dir(&self) -> PathBuf {
- let cid = self.cid;
- format!("{TEMPORARY_DIRECTORY}/{cid}").into()
- }
-}
-
-/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
-/// of this struct.
-#[derive(Debug, Default)]
-struct GlobalState {
- /// VM contexts currently allocated to running VMs. A CID is never recycled as long
- /// as there is a strong reference held by a GlobalVmContext.
- held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
-}
-
-impl GlobalState {
- /// Get the next available CID, or an error if we have run out. The last CID used is stored in
- /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
- /// Android is up.
- fn get_next_available_cid(&mut self) -> Result<Cid> {
- // Start trying to find a CID from the last used CID + 1. This ensures
- // that we do not eagerly recycle CIDs. It makes debugging easier but
- // also means that retrying to allocate a CID, eg. because it is
- // erroneously occupied by a process, will not recycle the same CID.
- let last_cid_prop =
- system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
- Ok(num) => {
- if is_valid_guest_cid(num) {
- Some(num)
- } else {
- error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
- None
- }
- }
- Err(_) => {
- error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
- None
- }
- });
-
- let first_cid = if let Some(last_cid) = last_cid_prop {
- if last_cid == GUEST_CID_MAX {
- GUEST_CID_MIN
- } else {
- last_cid + 1
- }
- } else {
- GUEST_CID_MIN
- };
-
- let cid = self
- .find_available_cid(first_cid..=GUEST_CID_MAX)
- .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
- .ok_or_else(|| anyhow!("Could not find an available CID."))?;
-
- system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
- Ok(cid)
- }
-
- fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
- where
- I: Iterator<Item = Cid>,
- {
- range.find(|cid| !self.held_contexts.contains_key(cid))
- }
-
- fn allocate_vm_context(
- &mut self,
- requester_uid: uid_t,
- requester_debug_pid: pid_t,
- ) -> Result<Strong<dyn IGlobalVmContext>> {
- // Garbage collect unused VM contexts.
- self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
-
- let cid = self.get_next_available_cid()?;
- let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
- create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
-
- self.held_contexts.insert(cid, Arc::downgrade(&instance));
- let binder = GlobalVmContext { instance, ..Default::default() };
- Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
- }
-}
-
-fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
- if path.as_path().exists() {
- remove_temporary_dir(path).unwrap_or_else(|e| {
- warn!("Could not delete temporary directory {:?}: {}", path, e);
- });
- }
- // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
- // If the chown() fails, this will leave behind an empty directory that will get removed
- // at the next attempt, or if virtualizationservice is restarted.
- create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
- chown(path, Some(Uid::from_raw(requester_uid)), None)
- .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
- Ok(())
-}
-
-/// Removes a directory owned by a different user by first changing its owner back
-/// to VirtualizationService.
-pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
- if !path.as_path().is_dir() {
- bail!("Path {:?} is not a directory", path);
- }
- chown(path, Some(Uid::current()), None)?;
- set_permissions(path, Permissions::from_mode(0o700))?;
- remove_temporary_files(path)?;
- remove_dir(path)?;
- Ok(())
-}
-
pub fn remove_temporary_files(path: &PathBuf) -> Result<()> {
for dir_entry in read_dir(path)? {
remove_file(dir_entry?.path())?;
@@ -368,28 +121,6 @@
Ok(())
}
-/// Implementation of the AIDL `IGlobalVmContext` interface.
-#[derive(Debug, Default)]
-struct GlobalVmContext {
- /// Strong reference to the context's instance data structure.
- instance: Arc<GlobalVmInstance>,
- /// Keeps our service process running as long as this VM context exists.
- #[allow(dead_code)]
- lazy_service_guard: LazyServiceGuard,
-}
-
-impl Interface for GlobalVmContext {}
-
-impl IGlobalVmContext for GlobalVmContext {
- fn getCid(&self) -> binder::Result<i32> {
- Ok(self.instance.cid as i32)
- }
-
- fn getTemporaryDirectory(&self) -> binder::Result<String> {
- Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
- }
-}
-
/// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
#[derive(Debug, Default)]
pub struct VirtualizationService {
@@ -509,60 +240,7 @@
}
}
-fn handle_stream_connection_tombstoned() -> Result<()> {
- // Should not listen for tombstones on a guest VM's port.
- assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
- let listener =
- VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
- for incoming_stream in listener.incoming() {
- let mut incoming_stream = match incoming_stream {
- Err(e) => {
- warn!("invalid incoming connection: {:?}", e);
- continue;
- }
- Ok(s) => s,
- };
- std::thread::spawn(move || {
- if let Err(e) = handle_tombstone(&mut incoming_stream) {
- error!("Failed to write tombstone- {:?}", e);
- }
- });
- }
- Ok(())
-}
-
-fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
- if let Ok(addr) = stream.peer_addr() {
- info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
- }
- let tb_connection =
- TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
- .context("Failed to connect to tombstoned")?;
- let mut text_output = tb_connection
- .text_output
- .as_ref()
- .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
- let mut num_bytes_read = 0;
- loop {
- let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
- let n = stream
- .read(&mut chunk_recv)
- .context("Failed to read tombstone data from Vsock stream")?;
- if n == 0 {
- break;
- }
- num_bytes_read += n;
- text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
- }
- info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
- tb_connection.notify_completion()?;
- Ok(())
-}
-
impl VirtualizationService {
- // TODO(b/245727626): Remove after the source files for virtualizationservice
- // and virtmgr binaries are split from each other.
- #[allow(dead_code)]
pub fn init() -> VirtualizationService {
VirtualizationService::default()
}
@@ -970,11 +648,6 @@
}
}
-/// Check whether the caller of the current Binder method is allowed to call debug methods.
-fn check_debug_access() -> binder::Result<()> {
- check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
-}
-
/// Check whether the caller of the current Binder method is allowed to manage VMs
fn check_manage_access() -> binder::Result<()> {
check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
diff --git a/virtualizationservice/src/aidl_vs.rs b/virtualizationservice/src/aidl_vs.rs
new file mode 100644
index 0000000..1266ff2
--- /dev/null
+++ b/virtualizationservice/src/aidl_vs.rs
@@ -0,0 +1,388 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Implementation of the AIDL interface of the VirtualizationService.
+
+use crate::{get_calling_pid, get_calling_uid};
+use crate::atom_vs::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
+use android_os_permissions_aidl::aidl::android::os::IPermissionController;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+ AtomVmBooted::AtomVmBooted,
+ AtomVmCreationRequested::AtomVmCreationRequested,
+ AtomVmExited::AtomVmExited,
+ IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
+ IVirtualizationServiceInternal::IVirtualizationServiceInternal,
+};
+use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
+use anyhow::{anyhow, bail, Context, Result};
+use binder::{self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong};
+use libc::VMADDR_CID_HOST;
+use log::{error, info, warn};
+use rustutils::system_properties;
+use std::collections::HashMap;
+use std::fs::{create_dir, read_dir, remove_dir, remove_file, set_permissions, Permissions};
+use std::io::{Read, Write};
+use std::os::unix::fs::PermissionsExt;
+use std::os::unix::raw::{pid_t, uid_t};
+use std::path::PathBuf;
+use std::sync::{Arc, Mutex, Weak};
+use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
+use vsock::{VsockListener, VsockStream};
+use nix::unistd::{chown, Uid};
+
+/// The unique ID of a VM used (together with a port number) for vsock communication.
+pub type Cid = u32;
+
+pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
+
+/// Directory in which to write disk image files used while running VMs.
+pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
+
+/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
+/// are reserved for the host or other usage.
+const GUEST_CID_MIN: Cid = 2048;
+const GUEST_CID_MAX: Cid = 65535;
+
+const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
+
+const CHUNK_RECV_MAX_LEN: usize = 1024;
+
+fn is_valid_guest_cid(cid: Cid) -> bool {
+ (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
+}
+
+/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
+/// singleton servers, like tombstone receiver.
+#[derive(Debug, Default)]
+pub struct VirtualizationServiceInternal {
+ state: Arc<Mutex<GlobalState>>,
+}
+
+impl VirtualizationServiceInternal {
+ pub fn init() -> VirtualizationServiceInternal {
+ let service = VirtualizationServiceInternal::default();
+
+ std::thread::spawn(|| {
+ if let Err(e) = handle_stream_connection_tombstoned() {
+ warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
+ }
+ });
+
+ service
+ }
+}
+
+impl Interface for VirtualizationServiceInternal {}
+
+impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
+ fn removeMemlockRlimit(&self) -> binder::Result<()> {
+ let pid = get_calling_pid();
+ let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
+
+ // SAFETY - borrowing the new limit struct only
+ let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
+
+ match ret {
+ 0 => Ok(()),
+ -1 => Err(Status::new_exception_str(
+ ExceptionCode::ILLEGAL_STATE,
+ Some(std::io::Error::last_os_error().to_string()),
+ )),
+ n => Err(Status::new_exception_str(
+ ExceptionCode::ILLEGAL_STATE,
+ Some(format!("Unexpected return value from prlimit(): {n}")),
+ )),
+ }
+ }
+
+ fn allocateGlobalVmContext(
+ &self,
+ requester_debug_pid: i32,
+ ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
+ check_manage_access()?;
+
+ let requester_uid = get_calling_uid();
+ let requester_debug_pid = requester_debug_pid as pid_t;
+ let state = &mut *self.state.lock().unwrap();
+ state.allocate_vm_context(requester_uid, requester_debug_pid).map_err(|e| {
+ Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+ })
+ }
+
+ fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
+ forward_vm_booted_atom(atom);
+ Ok(())
+ }
+
+ fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
+ forward_vm_creation_atom(atom);
+ Ok(())
+ }
+
+ fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
+ forward_vm_exited_atom(atom);
+ Ok(())
+ }
+
+ fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
+ check_debug_access()?;
+
+ let state = &mut *self.state.lock().unwrap();
+ let cids = state
+ .held_contexts
+ .iter()
+ .filter_map(|(_, inst)| Weak::upgrade(inst))
+ .map(|vm| VirtualMachineDebugInfo {
+ cid: vm.cid as i32,
+ temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
+ requesterUid: vm.requester_uid as i32,
+ requesterPid: vm.requester_debug_pid as i32,
+ })
+ .collect();
+ Ok(cids)
+ }
+}
+
+#[derive(Debug, Default)]
+struct GlobalVmInstance {
+ /// The unique CID assigned to the VM for vsock communication.
+ cid: Cid,
+ /// UID of the client who requested this VM instance.
+ requester_uid: uid_t,
+ /// PID of the client who requested this VM instance.
+ requester_debug_pid: pid_t,
+}
+
+impl GlobalVmInstance {
+ fn get_temp_dir(&self) -> PathBuf {
+ let cid = self.cid;
+ format!("{TEMPORARY_DIRECTORY}/{cid}").into()
+ }
+}
+
+/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
+/// of this struct.
+#[derive(Debug, Default)]
+struct GlobalState {
+ /// VM contexts currently allocated to running VMs. A CID is never recycled as long
+ /// as there is a strong reference held by a GlobalVmContext.
+ held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
+}
+
+impl GlobalState {
+ /// Get the next available CID, or an error if we have run out. The last CID used is stored in
+ /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
+ /// Android is up.
+ fn get_next_available_cid(&mut self) -> Result<Cid> {
+ // Start trying to find a CID from the last used CID + 1. This ensures
+ // that we do not eagerly recycle CIDs. It makes debugging easier but
+ // also means that retrying to allocate a CID, eg. because it is
+ // erroneously occupied by a process, will not recycle the same CID.
+ let last_cid_prop =
+ system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
+ Ok(num) => {
+ if is_valid_guest_cid(num) {
+ Some(num)
+ } else {
+ error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
+ None
+ }
+ }
+ Err(_) => {
+ error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
+ None
+ }
+ });
+
+ let first_cid = if let Some(last_cid) = last_cid_prop {
+ if last_cid == GUEST_CID_MAX {
+ GUEST_CID_MIN
+ } else {
+ last_cid + 1
+ }
+ } else {
+ GUEST_CID_MIN
+ };
+
+ let cid = self
+ .find_available_cid(first_cid..=GUEST_CID_MAX)
+ .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
+ .ok_or_else(|| anyhow!("Could not find an available CID."))?;
+
+ system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
+ Ok(cid)
+ }
+
+ fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
+ where
+ I: Iterator<Item = Cid>,
+ {
+ range.find(|cid| !self.held_contexts.contains_key(cid))
+ }
+
+ fn allocate_vm_context(
+ &mut self,
+ requester_uid: uid_t,
+ requester_debug_pid: pid_t,
+ ) -> Result<Strong<dyn IGlobalVmContext>> {
+ // Garbage collect unused VM contexts.
+ self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
+
+ let cid = self.get_next_available_cid()?;
+ let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
+ create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
+
+ self.held_contexts.insert(cid, Arc::downgrade(&instance));
+ let binder = GlobalVmContext { instance, ..Default::default() };
+ Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
+ }
+}
+
+fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
+ if path.as_path().exists() {
+ remove_temporary_dir(path).unwrap_or_else(|e| {
+ warn!("Could not delete temporary directory {:?}: {}", path, e);
+ });
+ }
+ // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
+ // If the chown() fails, this will leave behind an empty directory that will get removed
+ // at the next attempt, or if virtualizationservice is restarted.
+ create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
+ chown(path, Some(Uid::from_raw(requester_uid)), None)
+ .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
+ Ok(())
+}
+
+/// Removes a directory owned by a different user by first changing its owner back
+/// to VirtualizationService.
+pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
+ if !path.as_path().is_dir() {
+ bail!("Path {:?} is not a directory", path);
+ }
+ chown(path, Some(Uid::current()), None)?;
+ set_permissions(path, Permissions::from_mode(0o700))?;
+ remove_temporary_files(path)?;
+ remove_dir(path)?;
+ Ok(())
+}
+
+pub fn remove_temporary_files(path: &PathBuf) -> Result<()> {
+ for dir_entry in read_dir(path)? {
+ remove_file(dir_entry?.path())?;
+ }
+ Ok(())
+}
+
+/// Implementation of the AIDL `IGlobalVmContext` interface.
+#[derive(Debug, Default)]
+struct GlobalVmContext {
+ /// Strong reference to the context's instance data structure.
+ instance: Arc<GlobalVmInstance>,
+ /// Keeps our service process running as long as this VM context exists.
+ #[allow(dead_code)]
+ lazy_service_guard: LazyServiceGuard,
+}
+
+impl Interface for GlobalVmContext {}
+
+impl IGlobalVmContext for GlobalVmContext {
+ fn getCid(&self) -> binder::Result<i32> {
+ Ok(self.instance.cid as i32)
+ }
+
+ fn getTemporaryDirectory(&self) -> binder::Result<String> {
+ Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
+ }
+}
+
+fn handle_stream_connection_tombstoned() -> Result<()> {
+ // Should not listen for tombstones on a guest VM's port.
+ assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
+ let listener =
+ VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
+ for incoming_stream in listener.incoming() {
+ let mut incoming_stream = match incoming_stream {
+ Err(e) => {
+ warn!("invalid incoming connection: {:?}", e);
+ continue;
+ }
+ Ok(s) => s,
+ };
+ std::thread::spawn(move || {
+ if let Err(e) = handle_tombstone(&mut incoming_stream) {
+ error!("Failed to write tombstone- {:?}", e);
+ }
+ });
+ }
+ Ok(())
+}
+
+fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
+ if let Ok(addr) = stream.peer_addr() {
+ info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
+ }
+ let tb_connection =
+ TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
+ .context("Failed to connect to tombstoned")?;
+ let mut text_output = tb_connection
+ .text_output
+ .as_ref()
+ .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
+ let mut num_bytes_read = 0;
+ loop {
+ let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
+ let n = stream
+ .read(&mut chunk_recv)
+ .context("Failed to read tombstone data from Vsock stream")?;
+ if n == 0 {
+ break;
+ }
+ num_bytes_read += n;
+ text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
+ }
+ info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
+ tb_connection.notify_completion()?;
+ Ok(())
+}
+
+/// Checks whether the caller has a specific permission
+fn check_permission(perm: &str) -> binder::Result<()> {
+ let calling_pid = get_calling_pid();
+ let calling_uid = get_calling_uid();
+ // Root can do anything
+ if calling_uid == 0 {
+ return Ok(());
+ }
+ let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
+ binder::get_interface("permission")?;
+ if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
+ Ok(())
+ } else {
+ Err(Status::new_exception_str(
+ ExceptionCode::SECURITY,
+ Some(format!("does not have the {} permission", perm)),
+ ))
+ }
+}
+
+/// Check whether the caller of the current Binder method is allowed to call debug methods.
+fn check_debug_access() -> binder::Result<()> {
+ check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
+}
+
+/// Check whether the caller of the current Binder method is allowed to manage VMs
+fn check_manage_access() -> binder::Result<()> {
+ check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
+}
diff --git a/virtualizationservice/src/atom.rs b/virtualizationservice/src/atom.rs
index e430c74..c33f262 100644
--- a/virtualizationservice/src/atom.rs
+++ b/virtualizationservice/src/atom.rs
@@ -31,10 +31,9 @@
};
use anyhow::{anyhow, Result};
use binder::ParcelFileDescriptor;
-use log::{trace, warn};
+use log::warn;
use microdroid_payload_config::VmPayloadConfig;
-use rustutils::system_properties;
-use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
+use statslog_virtualization_rust::vm_creation_requested;
use std::thread;
use std::time::{Duration, SystemTime};
use zip::ZipArchive;
@@ -131,39 +130,6 @@
});
}
-pub fn forward_vm_creation_atom(atom: &AtomVmCreationRequested) {
- let config_type = match atom.configType {
- x if x == vm_creation_requested::ConfigType::VirtualMachineAppConfig as i32 => {
- vm_creation_requested::ConfigType::VirtualMachineAppConfig
- }
- x if x == vm_creation_requested::ConfigType::VirtualMachineRawConfig as i32 => {
- vm_creation_requested::ConfigType::VirtualMachineRawConfig
- }
- _ => vm_creation_requested::ConfigType::UnknownConfig,
- };
- let vm_creation_requested = vm_creation_requested::VmCreationRequested {
- uid: atom.uid,
- vm_identifier: &atom.vmIdentifier,
- hypervisor: vm_creation_requested::Hypervisor::Pkvm,
- is_protected: atom.isProtected,
- creation_succeeded: atom.creationSucceeded,
- binder_exception_code: atom.binderExceptionCode,
- config_type,
- num_cpus: atom.numCpus,
- cpu_affinity: "", // deprecated
- memory_mib: atom.memoryMib,
- apexes: &atom.apexes,
- // TODO(seungjaeyoo) Fill information about task_profile
- // TODO(seungjaeyoo) Fill information about disk_image for raw config
- };
-
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_creation_requested.stats_write() {
- Err(e) => warn!("statslog_rust failed with error: {}", e),
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
-}
-
/// Write the stats of VM boot to statsd
/// The function creates a separate thread which waits fro statsd to start to push atom
pub fn write_vm_booted_stats(
@@ -187,20 +153,6 @@
});
}
-pub fn forward_vm_booted_atom(atom: &AtomVmBooted) {
- let vm_booted = vm_booted::VmBooted {
- uid: atom.uid,
- vm_identifier: &atom.vmIdentifier,
- elapsed_time_millis: atom.elapsedTimeMillis,
- };
-
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_booted.stats_write() {
- Err(e) => warn!("statslog_rust failed with error: {}", e),
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
-}
-
/// Write the stats of VM exit to statsd
/// The function creates a separate thread which waits fro statsd to start to push atom
pub fn write_vm_exited_stats(
@@ -232,81 +184,3 @@
});
});
}
-
-pub fn forward_vm_exited_atom(atom: &AtomVmExited) {
- let death_reason = match atom.deathReason {
- DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
- DeathReason::KILLED => vm_exited::DeathReason::Killed,
- DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
- DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
- DeathReason::START_FAILED => vm_exited::DeathReason::Error,
- DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
- DeathReason::CRASH => vm_exited::DeathReason::Crash,
- DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
- vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
- }
- DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
- vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
- }
- DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
- vm_exited::DeathReason::BootloaderPublicKeyMismatch
- }
- DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
- vm_exited::DeathReason::BootloaderInstanceImageChanged
- }
- DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
- vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
- }
- DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
- vm_exited::DeathReason::MicrodroidPayloadHasChanged
- }
- DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
- vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
- }
- DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
- vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
- }
- DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
- vm_exited::DeathReason::MicrodroidUnknownRuntimeError
- }
- DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
- _ => vm_exited::DeathReason::Unknown,
- };
-
- let vm_exited = vm_exited::VmExited {
- uid: atom.uid,
- vm_identifier: &atom.vmIdentifier,
- elapsed_time_millis: atom.elapsedTimeMillis,
- death_reason,
- guest_time_millis: atom.guestTimeMillis,
- rss_vm_kb: atom.rssVmKb,
- rss_crosvm_kb: atom.rssCrosvmKb,
- exit_signal: atom.exitSignal,
- };
-
- wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
- match vm_exited.stats_write() {
- Err(e) => warn!("statslog_rust failed with error: {}", e),
- Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
- }
-}
-
-fn wait_for_statsd() -> Result<()> {
- let mut prop = system_properties::PropertyWatcher::new("init.svc.statsd")?;
- loop {
- prop.wait()?;
- match system_properties::read("init.svc.statsd")? {
- Some(s) => {
- if s == "running" {
- break;
- }
- }
- None => {
- // This case never really happens because
- // prop.wait() waits for property to be non-null.
- break;
- }
- }
- }
- Ok(())
-}
diff --git a/virtualizationservice/src/atom_vs.rs b/virtualizationservice/src/atom_vs.rs
new file mode 100644
index 0000000..47a1603
--- /dev/null
+++ b/virtualizationservice/src/atom_vs.rs
@@ -0,0 +1,151 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Functions for creating and collecting atoms.
+
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
+use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
+ AtomVmBooted::AtomVmBooted,
+ AtomVmCreationRequested::AtomVmCreationRequested,
+ AtomVmExited::AtomVmExited,
+};
+use anyhow::Result;
+use log::{trace, warn};
+use rustutils::system_properties;
+use statslog_virtualization_rust::{vm_booted, vm_creation_requested, vm_exited};
+
+pub fn forward_vm_creation_atom(atom: &AtomVmCreationRequested) {
+ let config_type = match atom.configType {
+ x if x == vm_creation_requested::ConfigType::VirtualMachineAppConfig as i32 => {
+ vm_creation_requested::ConfigType::VirtualMachineAppConfig
+ }
+ x if x == vm_creation_requested::ConfigType::VirtualMachineRawConfig as i32 => {
+ vm_creation_requested::ConfigType::VirtualMachineRawConfig
+ }
+ _ => vm_creation_requested::ConfigType::UnknownConfig,
+ };
+ let vm_creation_requested = vm_creation_requested::VmCreationRequested {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ hypervisor: vm_creation_requested::Hypervisor::Pkvm,
+ is_protected: atom.isProtected,
+ creation_succeeded: atom.creationSucceeded,
+ binder_exception_code: atom.binderExceptionCode,
+ config_type,
+ num_cpus: atom.numCpus,
+ cpu_affinity: "", // deprecated
+ memory_mib: atom.memoryMib,
+ apexes: &atom.apexes,
+ // TODO(seungjaeyoo) Fill information about task_profile
+ // TODO(seungjaeyoo) Fill information about disk_image for raw config
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_creation_requested.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
+pub fn forward_vm_booted_atom(atom: &AtomVmBooted) {
+ let vm_booted = vm_booted::VmBooted {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ elapsed_time_millis: atom.elapsedTimeMillis,
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_booted.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
+pub fn forward_vm_exited_atom(atom: &AtomVmExited) {
+ let death_reason = match atom.deathReason {
+ DeathReason::INFRASTRUCTURE_ERROR => vm_exited::DeathReason::InfrastructureError,
+ DeathReason::KILLED => vm_exited::DeathReason::Killed,
+ DeathReason::UNKNOWN => vm_exited::DeathReason::Unknown,
+ DeathReason::SHUTDOWN => vm_exited::DeathReason::Shutdown,
+ DeathReason::START_FAILED => vm_exited::DeathReason::Error,
+ DeathReason::REBOOT => vm_exited::DeathReason::Reboot,
+ DeathReason::CRASH => vm_exited::DeathReason::Crash,
+ DeathReason::PVM_FIRMWARE_PUBLIC_KEY_MISMATCH => {
+ vm_exited::DeathReason::PvmFirmwarePublicKeyMismatch
+ }
+ DeathReason::PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED => {
+ vm_exited::DeathReason::PvmFirmwareInstanceImageChanged
+ }
+ DeathReason::BOOTLOADER_PUBLIC_KEY_MISMATCH => {
+ vm_exited::DeathReason::BootloaderPublicKeyMismatch
+ }
+ DeathReason::BOOTLOADER_INSTANCE_IMAGE_CHANGED => {
+ vm_exited::DeathReason::BootloaderInstanceImageChanged
+ }
+ DeathReason::MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE => {
+ vm_exited::DeathReason::MicrodroidFailedToConnectToVirtualizationService
+ }
+ DeathReason::MICRODROID_PAYLOAD_HAS_CHANGED => {
+ vm_exited::DeathReason::MicrodroidPayloadHasChanged
+ }
+ DeathReason::MICRODROID_PAYLOAD_VERIFICATION_FAILED => {
+ vm_exited::DeathReason::MicrodroidPayloadVerificationFailed
+ }
+ DeathReason::MICRODROID_INVALID_PAYLOAD_CONFIG => {
+ vm_exited::DeathReason::MicrodroidInvalidPayloadConfig
+ }
+ DeathReason::MICRODROID_UNKNOWN_RUNTIME_ERROR => {
+ vm_exited::DeathReason::MicrodroidUnknownRuntimeError
+ }
+ DeathReason::HANGUP => vm_exited::DeathReason::Hangup,
+ _ => vm_exited::DeathReason::Unknown,
+ };
+
+ let vm_exited = vm_exited::VmExited {
+ uid: atom.uid,
+ vm_identifier: &atom.vmIdentifier,
+ elapsed_time_millis: atom.elapsedTimeMillis,
+ death_reason,
+ guest_time_millis: atom.guestTimeMillis,
+ rss_vm_kb: atom.rssVmKb,
+ rss_crosvm_kb: atom.rssCrosvmKb,
+ exit_signal: atom.exitSignal,
+ };
+
+ wait_for_statsd().unwrap_or_else(|e| warn!("failed to wait for statsd with error: {}", e));
+ match vm_exited.stats_write() {
+ Err(e) => warn!("statslog_rust failed with error: {}", e),
+ Ok(_) => trace!("statslog_rust succeeded for virtualization service"),
+ }
+}
+
+fn wait_for_statsd() -> Result<()> {
+ let mut prop = system_properties::PropertyWatcher::new("init.svc.statsd")?;
+ loop {
+ prop.wait()?;
+ match system_properties::read("init.svc.statsd")? {
+ Some(s) => {
+ if s == "running" {
+ break;
+ }
+ }
+ None => {
+ // This case never really happens because
+ // prop.wait() waits for property to be non-null.
+ break;
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 35eeff3..00c86f3 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -14,14 +14,13 @@
//! Android VirtualizationService
-mod aidl;
-mod atom;
-mod composite;
-mod crosvm;
-mod payload;
-mod selinux;
+mod aidl_vs;
+mod atom_vs;
-use crate::aidl::{remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY, VirtualizationServiceInternal};
+use crate::aidl_vs::{
+ remove_temporary_dir, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY,
+ VirtualizationServiceInternal
+};
use android_logger::{Config, FilterBuilder};
use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal::BnVirtualizationServiceInternal;
use anyhow::Error;