Rename virtualizationservice files
Files that belonged to VS were given a '_vs' suffix in a previous CL.
This is now not needed anymore, so rename the files back.
Bug: 245727626
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: I522a9c9645453b5e8b2b64cac3d922680715284e
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
new file mode 100644
index 0000000..43b7616
--- /dev/null
+++ b/virtualizationservice/src/aidl.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::{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")
+}