• 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::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
21 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
22     DeathReason::DeathReason,
23     IVirtualMachine::IVirtualMachine,
24     IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
25     IVirtualizationService::IVirtualizationService,
26     VirtualMachineAppConfig::{DebugLevel::DebugLevel, VirtualMachineAppConfig},
27     VirtualMachineConfig::VirtualMachineConfig,
28 };
29 use android_system_virtualizationservice::binder::{
30     wait_for_interface, BinderFeatures, DeathRecipient, IBinder, Interface, ParcelFileDescriptor,
31     Result as BinderResult, Strong,
32 };
33 use anyhow::{anyhow, bail, Context, Result};
34 use binder::{
35     unstable_api::{new_spibinder, AIBinder},
36     FromIBinder,
37 };
38 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
39 use log::{info, warn};
40 use rustutils::system_properties;
41 use std::fs::{self, File};
42 use std::io::{BufRead, BufReader};
43 use std::num::NonZeroU32;
44 use std::os::raw;
45 use std::os::unix::io::IntoRawFd;
46 use std::path::{Path, PathBuf};
47 use std::sync::{Arc, Condvar, Mutex};
48 use std::thread;
49 
50 /// This owns an instance of the CompOS VM.
51 pub struct VmInstance {
52     #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
53     vm: Strong<dyn IVirtualMachine>,
54     cid: i32,
55 }
56 
57 /// Parameters to be used when creating a virtual machine instance.
58 #[derive(Default, Debug, Clone)]
59 pub struct VmParameters {
60     /// Whether the VM should be debuggable.
61     pub debug_mode: bool,
62     /// Number of vCPUs to have in the VM. If None, defaults to 1.
63     pub cpus: Option<NonZeroU32>,
64     /// Comma separated list of host CPUs where vCPUs are assigned to. If None, any host CPU can be
65     /// used to run any vCPU.
66     pub cpu_set: Option<String>,
67     /// List of task profiles to apply to the VM
68     pub task_profiles: Vec<String>,
69     /// If present, overrides the path to the VM config JSON file
70     pub config_path: Option<String>,
71     /// If present, overrides the amount of RAM to give the VM
72     pub memory_mib: Option<i32>,
73     /// Never save VM logs to files.
74     pub never_log: bool,
75 }
76 
77 impl VmInstance {
78     /// Return a new connection to the Virtualization Service binder interface. This will start the
79     /// service if necessary.
connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>>80     pub fn connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>> {
81         wait_for_interface::<dyn IVirtualizationService>("android.system.virtualizationservice")
82             .context("Failed to find VirtualizationService")
83     }
84 
85     /// 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, parameters: &VmParameters, ) -> Result<VmInstance>86     pub fn start(
87         service: &dyn IVirtualizationService,
88         instance_image: File,
89         idsig: &Path,
90         idsig_manifest_apk: &Path,
91         parameters: &VmParameters,
92     ) -> Result<VmInstance> {
93         let protected_vm = want_protected_vm()?;
94 
95         let instance_fd = ParcelFileDescriptor::new(instance_image);
96 
97         let apex_dir = Path::new(COMPOS_APEX_ROOT);
98         let data_dir = Path::new(COMPOS_DATA_ROOT);
99 
100         let config_apk = Self::locate_config_apk(apex_dir)?;
101         let apk_fd = File::open(config_apk).context("Failed to open config APK file")?;
102         let apk_fd = ParcelFileDescriptor::new(apk_fd);
103         let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
104 
105         let manifest_apk_fd = File::open("/system/etc/security/fsverity/BuildManifest.apk")
106             .context("Failed to open build manifest APK file")?;
107         let manifest_apk_fd = ParcelFileDescriptor::new(manifest_apk_fd);
108         let idsig_manifest_apk_fd = prepare_idsig(service, &manifest_apk_fd, idsig_manifest_apk)?;
109 
110         let debug_level = match (protected_vm, parameters.debug_mode) {
111             (_, true) => DebugLevel::FULL,
112             (false, false) => DebugLevel::APP_ONLY,
113             (true, false) => DebugLevel::NONE,
114         };
115 
116         let (console_fd, log_fd) = if parameters.never_log || debug_level == DebugLevel::NONE {
117             (None, None)
118         } else {
119             // Console output and the system log output from the VM are redirected to file.
120             let console_fd = File::create(data_dir.join("vm_console.log"))
121                 .context("Failed to create console log file")?;
122             let log_fd = File::create(data_dir.join("vm.log"))
123                 .context("Failed to create system log file")?;
124             let console_fd = ParcelFileDescriptor::new(console_fd);
125             let log_fd = ParcelFileDescriptor::new(log_fd);
126             info!("Running in debug level {:?}", debug_level);
127             (Some(console_fd), Some(log_fd))
128         };
129 
130         let config_path = parameters.config_path.as_deref().unwrap_or(DEFAULT_VM_CONFIG_PATH);
131         let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
132             apk: Some(apk_fd),
133             idsig: Some(idsig_fd),
134             instanceImage: Some(instance_fd),
135             configPath: config_path.to_owned(),
136             debugLevel: debug_level,
137             extraIdsigs: vec![idsig_manifest_apk_fd],
138             protectedVm: protected_vm,
139             memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
140             numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
141             cpuAffinity: parameters.cpu_set.clone(),
142             taskProfiles: parameters.task_profiles.clone(),
143         });
144 
145         let vm = service
146             .createVm(&config, console_fd.as_ref(), log_fd.as_ref())
147             .context("Failed to create VM")?;
148         let vm_state = Arc::new(VmStateMonitor::default());
149 
150         let vm_state_clone = Arc::clone(&vm_state);
151         let mut death_recipient = DeathRecipient::new(move || {
152             vm_state_clone.set_died();
153             log::error!("VirtualizationService died");
154         });
155         // Note that dropping death_recipient cancels this, so we can't use a temporary here.
156         vm.as_binder().link_to_death(&mut death_recipient)?;
157 
158         let vm_state_clone = Arc::clone(&vm_state);
159         let callback = BnVirtualMachineCallback::new_binder(
160             VmCallback(vm_state_clone),
161             BinderFeatures::default(),
162         );
163         vm.registerCallback(&callback)?;
164 
165         vm.start()?;
166 
167         let cid = vm_state.wait_until_ready()?;
168 
169         Ok(VmInstance { vm, cid })
170     }
171 
locate_config_apk(apex_dir: &Path) -> Result<PathBuf>172     fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
173         // Our config APK will be in a directory under app, but the name of the directory is at the
174         // discretion of the build system. So just look in each sub-directory until we find it.
175         // (In practice there will be exactly one directory, so this shouldn't take long.)
176         let app_dir = apex_dir.join("app");
177         for dir in fs::read_dir(app_dir).context("Reading app dir")? {
178             let apk_file = dir?.path().join("CompOSPayloadApp.apk");
179             if apk_file.is_file() {
180                 return Ok(apk_file);
181             }
182         }
183 
184         bail!("Failed to locate CompOSPayloadApp.apk")
185     }
186 
187     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
get_service(&self) -> Result<Strong<dyn ICompOsService>>188     pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
189         let mut vsock_factory = VsockFactory::new(&*self.vm);
190 
191         let ibinder = vsock_factory
192             .connect_rpc_client()
193             .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
194 
195         FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
196     }
197 
198     /// Return the CID of the VM.
cid(&self) -> i32199     pub fn cid(&self) -> i32 {
200         // TODO: Do we actually need/use this?
201         self.cid
202     }
203 }
204 
prepare_idsig( service: &dyn IVirtualizationService, apk_fd: &ParcelFileDescriptor, idsig_path: &Path, ) -> Result<ParcelFileDescriptor>205 fn prepare_idsig(
206     service: &dyn IVirtualizationService,
207     apk_fd: &ParcelFileDescriptor,
208     idsig_path: &Path,
209 ) -> Result<ParcelFileDescriptor> {
210     if !idsig_path.exists() {
211         // Prepare idsig file via VirtualizationService
212         let idsig_file = File::create(idsig_path).context("Failed to create idsig file")?;
213         let idsig_fd = ParcelFileDescriptor::new(idsig_file);
214         service
215             .createOrUpdateIdsigFile(apk_fd, &idsig_fd)
216             .context("Failed to update idsig file")?;
217     }
218 
219     // Open idsig as read-only
220     let idsig_file = File::open(idsig_path).context("Failed to open idsig file")?;
221     let idsig_fd = ParcelFileDescriptor::new(idsig_file);
222     Ok(idsig_fd)
223 }
224 
want_protected_vm() -> Result<bool>225 fn want_protected_vm() -> Result<bool> {
226     let have_protected_vm =
227         system_properties::read_bool("ro.boot.hypervisor.protected_vm.supported", false)?;
228     if have_protected_vm {
229         info!("Starting protected VM");
230         return Ok(true);
231     }
232 
233     let is_debug_build = system_properties::read("ro.debuggable")?.as_deref().unwrap_or("0") == "1";
234     if !is_debug_build {
235         bail!("Protected VM not supported, unable to start VM");
236     }
237 
238     let have_unprotected_vm =
239         system_properties::read_bool("ro.boot.hypervisor.vm.supported", false)?;
240     if have_unprotected_vm {
241         warn!("Protected VM not supported, falling back to unprotected on debuggable build");
242         return Ok(false);
243     }
244 
245     bail!("No VM support available")
246 }
247 
248 struct VsockFactory<'a> {
249     vm: &'a dyn IVirtualMachine,
250 }
251 
252 impl<'a> VsockFactory<'a> {
new(vm: &'a dyn IVirtualMachine) -> Self253     fn new(vm: &'a dyn IVirtualMachine) -> Self {
254         Self { vm }
255     }
256 
connect_rpc_client(&mut self) -> Option<binder::SpIBinder>257     fn connect_rpc_client(&mut self) -> Option<binder::SpIBinder> {
258         let param = self.as_void_ptr();
259 
260         unsafe {
261             // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
262             // the ownership can be safely taken by new_spibinder.
263             // RpcPreconnectedClient does not take ownership of param, only passing it to
264             // request_fd.
265             let binder =
266                 binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
267                     as *mut AIBinder;
268             new_spibinder(binder)
269         }
270     }
271 
as_void_ptr(&mut self) -> *mut raw::c_void272     fn as_void_ptr(&mut self) -> *mut raw::c_void {
273         self as *mut _ as *mut raw::c_void
274     }
275 
try_new_vsock_fd(&self) -> Result<i32>276     fn try_new_vsock_fd(&self) -> Result<i32> {
277         let vsock = self.vm.connectVsock(COMPOS_VSOCK_PORT as i32)?;
278         // Ownership of the fd is transferred to binder
279         Ok(vsock.into_raw_fd())
280     }
281 
new_vsock_fd(&self) -> i32282     fn new_vsock_fd(&self) -> i32 {
283         self.try_new_vsock_fd().unwrap_or_else(|e| {
284             warn!("Connecting vsock failed: {}", e);
285             -1_i32
286         })
287     }
288 
request_fd(param: *mut raw::c_void) -> raw::c_int289     unsafe extern "C" fn request_fd(param: *mut raw::c_void) -> raw::c_int {
290         // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
291         // VsockFactory, with param taking the value returned by as_void_ptr (so a properly aligned
292         // non-null pointer to an initialized instance).
293         let vsock_factory = param as *mut Self;
294         vsock_factory.as_ref().unwrap().new_vsock_fd()
295     }
296 }
297 
298 #[derive(Debug, Default)]
299 struct VmState {
300     has_died: bool,
301     cid: Option<i32>,
302 }
303 
304 #[derive(Debug)]
305 struct VmStateMonitor {
306     mutex: Mutex<VmState>,
307     state_ready: Condvar,
308 }
309 
310 impl Default for VmStateMonitor {
default() -> Self311     fn default() -> Self {
312         Self { mutex: Mutex::new(Default::default()), state_ready: Condvar::new() }
313     }
314 }
315 
316 impl VmStateMonitor {
set_died(&self)317     fn set_died(&self) {
318         let mut state = self.mutex.lock().unwrap();
319         state.has_died = true;
320         state.cid = None;
321         drop(state); // Unlock the mutex prior to notifying
322         self.state_ready.notify_all();
323     }
324 
set_ready(&self, cid: i32)325     fn set_ready(&self, cid: i32) {
326         let mut state = self.mutex.lock().unwrap();
327         if state.has_died {
328             return;
329         }
330         state.cid = Some(cid);
331         drop(state); // Unlock the mutex prior to notifying
332         self.state_ready.notify_all();
333     }
334 
wait_until_ready(&self) -> Result<i32>335     fn wait_until_ready(&self) -> Result<i32> {
336         let (state, result) = self
337             .state_ready
338             .wait_timeout_while(
339                 self.mutex.lock().unwrap(),
340                 timeouts()?.vm_max_time_to_ready,
341                 |state| state.cid.is_none() && !state.has_died,
342             )
343             .unwrap();
344         if result.timed_out() {
345             bail!("Timed out waiting for VM")
346         }
347         state.cid.ok_or_else(|| anyhow!("VM died"))
348     }
349 }
350 
351 #[derive(Debug)]
352 struct VmCallback(Arc<VmStateMonitor>);
353 
354 impl Interface for VmCallback {}
355 
356 impl IVirtualMachineCallback for VmCallback {
onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()>357     fn onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()> {
358         self.0.set_died();
359         log::warn!("VM died, cid = {}, reason = {:?}", cid, reason);
360         Ok(())
361     }
362 
onPayloadStarted( &self, cid: i32, stream: Option<&ParcelFileDescriptor>, ) -> BinderResult<()>363     fn onPayloadStarted(
364         &self,
365         cid: i32,
366         stream: Option<&ParcelFileDescriptor>,
367     ) -> BinderResult<()> {
368         if let Some(pfd) = stream {
369             if let Err(e) = start_logging(pfd) {
370                 warn!("Can't log vm output: {}", e);
371             };
372         }
373         log::info!("VM payload started, cid = {}", cid);
374         Ok(())
375     }
376 
onPayloadReady(&self, cid: i32) -> BinderResult<()>377     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
378         self.0.set_ready(cid);
379         log::info!("VM payload ready, cid = {}", cid);
380         Ok(())
381     }
382 
onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()>383     fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
384         // This should probably never happen in our case, but if it does we means our VM is no
385         // longer running
386         self.0.set_died();
387         log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
388         Ok(())
389     }
390 
onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()>391     fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
392         self.0.set_died();
393         log::warn!("VM error, cid = {}, error code = {}, message = {}", cid, error_code, message,);
394         Ok(())
395     }
396 }
397 
start_logging(pfd: &ParcelFileDescriptor) -> Result<()>398 fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
399     let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
400     thread::spawn(move || {
401         for line in reader.lines() {
402             match line {
403                 Ok(line) => info!("VM: {}", line),
404                 Err(e) => {
405                     warn!("Reading VM output failed: {}", e);
406                     break;
407                 }
408             }
409         }
410     });
411     Ok(())
412 }
413