• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! Support for starting CompOS in a VM and connecting to the service
18 
19 use crate::timeouts::TIMEOUTS;
20 use crate::{
21     get_vm_config_path, BUILD_MANIFEST_APK_PATH, BUILD_MANIFEST_SYSTEM_EXT_APK_PATH,
22     COMPOS_APEX_ROOT, COMPOS_VSOCK_PORT,
23 };
24 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
25     CpuTopology::CpuTopology,
26     IVirtualizationService::IVirtualizationService,
27     VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
28     VirtualMachineConfig::VirtualMachineConfig,
29 };
30 use anyhow::{anyhow, bail, Context, Result};
31 use binder::{ParcelFileDescriptor, Strong};
32 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
33 use glob::glob;
34 use log::{info, warn};
35 use rustutils::system_properties;
36 use std::fs::File;
37 use std::path::{Path, PathBuf};
38 use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
39 
40 /// This owns an instance of the CompOS VM.
41 pub struct ComposClient(VmInstance);
42 
43 /// CPU topology configuration for a virtual machine.
44 #[derive(Default, Debug, Clone)]
45 pub enum VmCpuTopology {
46     /// Run VM with 1 vCPU only.
47     #[default]
48     OneCpu,
49     /// Run VM vCPU topology matching that of the host.
50     MatchHost,
51 }
52 
53 /// Parameters to be used when creating a virtual machine instance.
54 #[derive(Default, Debug, Clone)]
55 pub struct VmParameters {
56     /// The name of VM for identifying.
57     pub name: String,
58     /// Whether the VM should be debuggable.
59     pub debug_mode: bool,
60     /// CPU topology of the VM. Defaults to 1 vCPU.
61     pub cpu_topology: VmCpuTopology,
62     /// List of task profiles to apply to the VM
63     pub task_profiles: Vec<String>,
64     /// If present, overrides the amount of RAM to give the VM
65     pub memory_mib: Option<i32>,
66     /// Whether the VM prefers staged APEXes or activated ones (false; default)
67     pub prefer_staged: bool,
68 }
69 
70 impl ComposClient {
71     /// Start a new CompOS VM instance using the specified instance image file and parameters.
start( service: &dyn IVirtualizationService, instance_image: File, idsig: &Path, idsig_manifest_apk: &Path, idsig_manifest_ext_apk: &Path, parameters: &VmParameters, ) -> Result<Self>72     pub fn start(
73         service: &dyn IVirtualizationService,
74         instance_image: File,
75         idsig: &Path,
76         idsig_manifest_apk: &Path,
77         idsig_manifest_ext_apk: &Path,
78         parameters: &VmParameters,
79     ) -> Result<Self> {
80         let protected_vm = want_protected_vm()?;
81 
82         let instance_fd = ParcelFileDescriptor::new(instance_image);
83 
84         let apex_dir = Path::new(COMPOS_APEX_ROOT);
85 
86         let config_apk = locate_config_apk(apex_dir)?;
87         let apk_fd = File::open(config_apk).context("Failed to open config APK file")?;
88         let apk_fd = ParcelFileDescriptor::new(apk_fd);
89         let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
90 
91         let manifest_apk_fd = File::open(BUILD_MANIFEST_APK_PATH)
92             .context("Failed to open build manifest APK file")?;
93         let manifest_apk_fd = ParcelFileDescriptor::new(manifest_apk_fd);
94         let idsig_manifest_apk_fd = prepare_idsig(service, &manifest_apk_fd, idsig_manifest_apk)?;
95 
96         // Prepare a few things based on whether /system_ext exists, including:
97         // 1. generate the additional idsig FD for the APK from /system_ext, then pass to VS
98         // 2. select the correct VM config json
99         let (extra_idsigs, has_system_ext) =
100             if let Ok(manifest_ext_apk_fd) = File::open(BUILD_MANIFEST_SYSTEM_EXT_APK_PATH) {
101                 // Optional idsig in /system_ext is found, so prepare additionally.
102                 let manifest_ext_apk_fd = ParcelFileDescriptor::new(manifest_ext_apk_fd);
103                 let idsig_manifest_ext_apk_fd =
104                     prepare_idsig(service, &manifest_ext_apk_fd, idsig_manifest_ext_apk)?;
105 
106                 (vec![idsig_manifest_apk_fd, idsig_manifest_ext_apk_fd], true)
107             } else {
108                 (vec![idsig_manifest_apk_fd], false)
109             };
110         let config_path = get_vm_config_path(has_system_ext, parameters.prefer_staged);
111 
112         let debug_level = if parameters.debug_mode { DebugLevel::FULL } else { DebugLevel::NONE };
113 
114         let cpu_topology = match parameters.cpu_topology {
115             VmCpuTopology::OneCpu => CpuTopology::ONE_CPU,
116             VmCpuTopology::MatchHost => CpuTopology::MATCH_HOST,
117         };
118 
119         let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
120             name: parameters.name.clone(),
121             apk: Some(apk_fd),
122             idsig: Some(idsig_fd),
123             instanceImage: Some(instance_fd),
124             encryptedStorageImage: None,
125             payload: Payload::ConfigPath(config_path),
126             debugLevel: debug_level,
127             extraIdsigs: extra_idsigs,
128             protectedVm: protected_vm,
129             memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
130             cpuTopology: cpu_topology,
131             taskProfiles: parameters.task_profiles.clone(),
132             gdbPort: 0, // Don't start gdb-server
133         });
134 
135         // Let logs go to logcat.
136         let (console_fd, log_fd) = (None, None);
137         let callback = Box::new(Callback {});
138         let instance = VmInstance::create(service, &config, console_fd, log_fd, Some(callback))
139             .context("Failed to create VM")?;
140 
141         instance.start()?;
142 
143         let ready = instance.wait_until_ready(TIMEOUTS.vm_max_time_to_ready);
144         if ready == Err(VmWaitError::Finished) && debug_level != DebugLevel::NONE {
145             // The payload has (unexpectedly) finished, but the VM is still running. Give it
146             // some time to shutdown to maximize our chances of getting useful logs.
147             if let Some(death_reason) =
148                 instance.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit)
149             {
150                 bail!("VM died during startup - reason {:?}", death_reason);
151             }
152         }
153         ready?;
154 
155         Ok(Self(instance))
156     }
157 
158     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
connect_service(&self) -> Result<Strong<dyn ICompOsService>>159     pub fn connect_service(&self) -> Result<Strong<dyn ICompOsService>> {
160         self.0.connect_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
161     }
162 
163     /// Shut down the VM cleanly, by sending a quit request to the service, giving time for any
164     /// relevant logs to be written.
shutdown(self, service: Strong<dyn ICompOsService>)165     pub fn shutdown(self, service: Strong<dyn ICompOsService>) {
166         info!("Requesting CompOS VM to shutdown");
167         let _ = service.quit(); // If this fails, the VM is probably dying anyway
168         self.wait_for_shutdown();
169     }
170 
171     /// Wait for the instance to shut down. If it fails to shutdown within a reasonable time the
172     /// instance is dropped, which forcibly terminates it.
173     /// This should only be called when the instance has been requested to quit, or we believe that
174     /// it is already in the process of exiting due to some failure.
wait_for_shutdown(self)175     fn wait_for_shutdown(self) {
176         let death_reason = self.0.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit);
177         match death_reason {
178             Some(DeathReason::Shutdown) => info!("VM has exited normally"),
179             Some(reason) => warn!("VM died with reason {:?}", reason),
180             None => warn!("VM failed to exit, dropping"),
181         }
182     }
183 }
184 
locate_config_apk(apex_dir: &Path) -> Result<PathBuf>185 fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
186     // Our config APK will be in a directory under app, but the name of the directory is at the
187     // discretion of the build system. So just look in each sub-directory until we find it.
188     // (In practice there will be exactly one directory, so this shouldn't take long.)
189     let app_glob = apex_dir.join("app").join("**").join("CompOSPayloadApp*.apk");
190     let mut entries: Vec<PathBuf> =
191         glob(app_glob.to_str().ok_or_else(|| anyhow!("Invalid path: {}", app_glob.display()))?)
192             .context("failed to glob")?
193             .filter_map(|e| e.ok())
194             .collect();
195     if entries.len() > 1 {
196         bail!("Found more than one apk matching {}", app_glob.display());
197     }
198     match entries.pop() {
199         Some(path) => Ok(path),
200         None => Err(anyhow!("No apks match {}", app_glob.display())),
201     }
202 }
203 
prepare_idsig( service: &dyn IVirtualizationService, apk_fd: &ParcelFileDescriptor, idsig_path: &Path, ) -> Result<ParcelFileDescriptor>204 fn prepare_idsig(
205     service: &dyn IVirtualizationService,
206     apk_fd: &ParcelFileDescriptor,
207     idsig_path: &Path,
208 ) -> Result<ParcelFileDescriptor> {
209     if !idsig_path.exists() {
210         // Prepare idsig file via VirtualizationService
211         let idsig_file = File::create(idsig_path).context("Failed to create idsig file")?;
212         let idsig_fd = ParcelFileDescriptor::new(idsig_file);
213         service
214             .createOrUpdateIdsigFile(apk_fd, &idsig_fd)
215             .context("Failed to update idsig file")?;
216     }
217 
218     // Open idsig as read-only
219     let idsig_file = File::open(idsig_path).context("Failed to open idsig file")?;
220     let idsig_fd = ParcelFileDescriptor::new(idsig_file);
221     Ok(idsig_fd)
222 }
223 
want_protected_vm() -> Result<bool>224 fn want_protected_vm() -> Result<bool> {
225     let have_protected_vm =
226         system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
227     if have_protected_vm {
228         info!("Starting protected VM");
229         return Ok(true);
230     }
231 
232     let is_debug_build = system_properties::read("ro.debuggable")?.as_deref().unwrap_or("0") == "1";
233     if !is_debug_build {
234         bail!("Protected VM not supported, unable to start VM");
235     }
236 
237     let have_non_protected_vm =
238         system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
239     if have_non_protected_vm {
240         warn!("Protected VM not supported, falling back to non-protected on debuggable build");
241         return Ok(false);
242     }
243 
244     bail!("No VM support available")
245 }
246 
247 struct Callback {}
248 impl vmclient::VmCallback for Callback {
on_payload_started(&self, cid: i32)249     fn on_payload_started(&self, cid: i32) {
250         log::info!("VM payload started, cid = {}", cid);
251     }
252 
on_payload_ready(&self, cid: i32)253     fn on_payload_ready(&self, cid: i32) {
254         log::info!("VM payload ready, cid = {}", cid);
255     }
256 
on_payload_finished(&self, cid: i32, exit_code: i32)257     fn on_payload_finished(&self, cid: i32, exit_code: i32) {
258         log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
259     }
260 
on_error(&self, cid: i32, error_code: ErrorCode, message: &str)261     fn on_error(&self, cid: i32, error_code: ErrorCode, message: &str) {
262         log::warn!("VM error, cid = {}, error code = {:?}, message = {}", cid, error_code, message);
263     }
264 
on_died(&self, cid: i32, death_reason: DeathReason)265     fn on_died(&self, cid: i32, death_reason: DeathReason) {
266         log::warn!("VM died, cid = {}, reason = {:?}", cid, death_reason);
267     }
268 }
269