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