1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Implementation of the AIDL interface of the VirtualizationService.
16
17 use crate::{get_calling_pid, get_calling_uid, get_this_pid};
18 use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
19 use crate::composite::make_composite_image;
20 use crate::crosvm::{CrosvmConfig, DiskFile, DisplayConfig, GpuConfig, InputDeviceOption, PayloadState, VmContext, VmInstance, VmState};
21 use crate::debug_config::DebugConfig;
22 use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
23 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
24 use crate::selinux::{getfilecon, SeContext};
25 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
26 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
27 Certificate::Certificate,
28 DeathReason::DeathReason,
29 ErrorCode::ErrorCode,
30 };
31 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
32 AssignableDevice::AssignableDevice,
33 CpuTopology::CpuTopology,
34 DiskImage::DiskImage,
35 InputDevice::InputDevice,
36 IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
37 IVirtualMachineCallback::IVirtualMachineCallback,
38 IVirtualizationService::IVirtualizationService,
39 MemoryTrimLevel::MemoryTrimLevel,
40 Partition::Partition,
41 PartitionType::PartitionType,
42 VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
43 VirtualMachineConfig::VirtualMachineConfig,
44 VirtualMachineDebugInfo::VirtualMachineDebugInfo,
45 VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
46 VirtualMachineRawConfig::VirtualMachineRawConfig,
47 VirtualMachineState::VirtualMachineState,
48 };
49 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal::IVirtualizationServiceInternal;
50 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
51 BnVirtualMachineService, IVirtualMachineService,
52 };
53 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{BnSecretkeeper, ISecretkeeper};
54 use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::SecretId::SecretId;
55 use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
56 Arc::Arc as AuthgraphArc, IAuthGraphKeyExchange::IAuthGraphKeyExchange,
57 IAuthGraphKeyExchange::BnAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
58 Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
59 SessionInitiationInfo::SessionInitiationInfo,
60 };
61 use anyhow::{anyhow, bail, Context, Result};
62 use apkverify::{HashAlgorithm, V4Signature};
63 use avflog::LogResult;
64 use binder::{
65 self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor,
66 Status, StatusCode, Strong,
67 IntoBinderResult,
68 };
69 use cstr::cstr;
70 use glob::glob;
71 use lazy_static::lazy_static;
72 use log::{debug, error, info, warn};
73 use microdroid_payload_config::{ApkConfig, Task, TaskType, VmPayloadConfig};
74 use nix::unistd::pipe;
75 use rpcbinder::RpcServer;
76 use rustutils::system_properties;
77 use semver::VersionReq;
78 use std::collections::HashSet;
79 use std::convert::TryInto;
80 use std::fs;
81 use std::ffi::CStr;
82 use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
83 use std::io::{BufRead, BufReader, Error, ErrorKind, Seek, SeekFrom, Write};
84 use std::iter;
85 use std::num::{NonZeroU16, NonZeroU32};
86 use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
87 use std::os::unix::raw::pid_t;
88 use std::path::{Path, PathBuf};
89 use std::sync::{Arc, Mutex, Weak};
90 use vbmeta::VbMetaImage;
91 use vmconfig::{VmConfig, get_debug_level};
92 use vsock::VsockStream;
93 use zip::ZipArchive;
94
95 /// The unique ID of a VM used (together with a port number) for vsock communication.
96 pub type Cid = u32;
97
98 pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
99
100 /// The size of zero.img.
101 /// Gaps in composite disk images are filled with a shared zero.img.
102 const ZERO_FILLER_SIZE: u64 = 4096;
103
104 /// Magic string for the instance image
105 const ANDROID_VM_INSTANCE_MAGIC: &str = "Android-VM-instance";
106
107 /// Version of the instance image format
108 const ANDROID_VM_INSTANCE_VERSION: u16 = 1;
109
110 const MICRODROID_OS_NAME: &str = "microdroid";
111
112 const SECRETKEEPER_IDENTIFIER: &str =
113 "android.hardware.security.secretkeeper.ISecretkeeper/default";
114
115 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
116
117 /// crosvm requires all partitions to be a multiple of 4KiB.
118 const PARTITION_GRANULARITY_BYTES: u64 = 4096;
119
120 const VM_REFERENCE_DT_ON_HOST_PATH: &str = "/proc/device-tree/avf/reference";
121
122 lazy_static! {
123 pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
124 wait_for_interface(BINDER_SERVICE_IDENTIFIER)
125 .expect("Could not connect to VirtualizationServiceInternal");
126 static ref SUPPORTED_OS_NAMES: HashSet<String> =
127 get_supported_os_names().expect("Failed to get list of supported os names");
128 }
129
create_or_update_idsig_file( input_fd: &ParcelFileDescriptor, idsig_fd: &ParcelFileDescriptor, ) -> Result<()>130 fn create_or_update_idsig_file(
131 input_fd: &ParcelFileDescriptor,
132 idsig_fd: &ParcelFileDescriptor,
133 ) -> Result<()> {
134 let mut input = clone_file(input_fd)?;
135 let metadata = input.metadata().context("failed to get input metadata")?;
136 if !metadata.is_file() {
137 bail!("input is not a regular file");
138 }
139 let mut sig =
140 V4Signature::create(&mut input, get_current_sdk()?, 4096, &[], HashAlgorithm::SHA256)
141 .context("failed to create idsig")?;
142
143 let mut output = clone_file(idsig_fd)?;
144
145 // Optimization. We don't have to update idsig file whenever a VM is started. Don't update it,
146 // if the idsig file already has the same APK digest.
147 if output.metadata()?.len() > 0 {
148 if let Ok(out_sig) = V4Signature::from_idsig(&mut output) {
149 if out_sig.signing_info.apk_digest == sig.signing_info.apk_digest {
150 debug!("idsig {:?} is up-to-date with apk {:?}.", output, input);
151 return Ok(());
152 }
153 }
154 // if we fail to read v4signature from output, that's fine. User can pass a random file.
155 // We will anyway overwrite the file to the v4signature generated from input_fd.
156 }
157
158 output
159 .seek(SeekFrom::Start(0))
160 .context("failed to move cursor to start on the idsig output")?;
161 output.set_len(0).context("failed to set_len on the idsig output")?;
162 sig.write_into(&mut output).context("failed to write idsig")?;
163 Ok(())
164 }
165
get_current_sdk() -> Result<u32>166 fn get_current_sdk() -> Result<u32> {
167 let current_sdk = system_properties::read("ro.build.version.sdk")?;
168 let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
169 current_sdk.parse().context("Malformed SDK version")
170 }
171
remove_temporary_files(path: &PathBuf) -> Result<()>172 pub fn remove_temporary_files(path: &PathBuf) -> Result<()> {
173 for dir_entry in read_dir(path)? {
174 remove_file(dir_entry?.path())?;
175 }
176 Ok(())
177 }
178
179 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
180 #[derive(Debug, Default)]
181 pub struct VirtualizationService {
182 state: Arc<Mutex<State>>,
183 }
184
185 impl Interface for VirtualizationService {
dump(&self, writer: &mut dyn Write, _args: &[&CStr]) -> Result<(), StatusCode>186 fn dump(&self, writer: &mut dyn Write, _args: &[&CStr]) -> Result<(), StatusCode> {
187 check_permission("android.permission.DUMP").or(Err(StatusCode::PERMISSION_DENIED))?;
188 let state = &mut *self.state.lock().unwrap();
189 let vms = state.vms();
190 writeln!(writer, "Running {0} VMs:", vms.len()).or(Err(StatusCode::UNKNOWN_ERROR))?;
191 for vm in vms {
192 writeln!(writer, "VM CID: {}", vm.cid).or(Err(StatusCode::UNKNOWN_ERROR))?;
193 writeln!(writer, "\tState: {:?}", vm.vm_state.lock().unwrap())
194 .or(Err(StatusCode::UNKNOWN_ERROR))?;
195 writeln!(writer, "\tPayload state {:?}", vm.payload_state())
196 .or(Err(StatusCode::UNKNOWN_ERROR))?;
197 writeln!(writer, "\tProtected: {}", vm.protected).or(Err(StatusCode::UNKNOWN_ERROR))?;
198 writeln!(writer, "\ttemporary_directory: {}", vm.temporary_directory.to_string_lossy())
199 .or(Err(StatusCode::UNKNOWN_ERROR))?;
200 writeln!(writer, "\trequester_uid: {}", vm.requester_uid)
201 .or(Err(StatusCode::UNKNOWN_ERROR))?;
202 writeln!(writer, "\trequester_debug_pid: {}", vm.requester_debug_pid)
203 .or(Err(StatusCode::UNKNOWN_ERROR))?;
204 }
205 Ok(())
206 }
207 }
208 impl IVirtualizationService for VirtualizationService {
209 /// Creates (but does not start) a new VM with the given configuration, assigning it the next
210 /// available CID.
211 ///
212 /// Returns a binder `IVirtualMachine` object referring to it, as a handle for the client.
createVm( &self, config: &VirtualMachineConfig, console_out_fd: Option<&ParcelFileDescriptor>, console_in_fd: Option<&ParcelFileDescriptor>, log_fd: Option<&ParcelFileDescriptor>, ) -> binder::Result<Strong<dyn IVirtualMachine>>213 fn createVm(
214 &self,
215 config: &VirtualMachineConfig,
216 console_out_fd: Option<&ParcelFileDescriptor>,
217 console_in_fd: Option<&ParcelFileDescriptor>,
218 log_fd: Option<&ParcelFileDescriptor>,
219 ) -> binder::Result<Strong<dyn IVirtualMachine>> {
220 let mut is_protected = false;
221 let ret = self.create_vm_internal(
222 config,
223 console_out_fd,
224 console_in_fd,
225 log_fd,
226 &mut is_protected,
227 );
228 write_vm_creation_stats(config, is_protected, &ret);
229 ret
230 }
231
232 /// Allocate a new instance_id to the VM
allocateInstanceId(&self) -> binder::Result<[u8; 64]>233 fn allocateInstanceId(&self) -> binder::Result<[u8; 64]> {
234 check_manage_access()?;
235 GLOBAL_SERVICE.allocateInstanceId()
236 }
237
238 /// Initialise an empty partition image of the given size to be used as a writable partition.
initializeWritablePartition( &self, image_fd: &ParcelFileDescriptor, size_bytes: i64, partition_type: PartitionType, ) -> binder::Result<()>239 fn initializeWritablePartition(
240 &self,
241 image_fd: &ParcelFileDescriptor,
242 size_bytes: i64,
243 partition_type: PartitionType,
244 ) -> binder::Result<()> {
245 check_manage_access()?;
246 let size_bytes = size_bytes
247 .try_into()
248 .with_context(|| format!("Invalid size: {}", size_bytes))
249 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?;
250 let size_bytes = round_up(size_bytes, PARTITION_GRANULARITY_BYTES);
251 let mut image = clone_file(image_fd)?;
252 // initialize the file. Any data in the file will be erased.
253 image
254 .seek(SeekFrom::Start(0))
255 .context("failed to move cursor to start")
256 .or_service_specific_exception(-1)?;
257 image.set_len(0).context("Failed to reset a file").or_service_specific_exception(-1)?;
258 // Set the file length. In most filesystems, this will not allocate any physical disk
259 // space, it will only change the logical size.
260 image
261 .set_len(size_bytes)
262 .context("Failed to extend file")
263 .or_service_specific_exception(-1)?;
264
265 match partition_type {
266 PartitionType::RAW => Ok(()),
267 PartitionType::ANDROID_VM_INSTANCE => format_as_android_vm_instance(&mut image),
268 PartitionType::ENCRYPTEDSTORE => format_as_encryptedstore(&mut image),
269 _ => Err(Error::new(
270 ErrorKind::Unsupported,
271 format!("Unsupported partition type {:?}", partition_type),
272 )),
273 }
274 .with_context(|| format!("Failed to initialize partition as {:?}", partition_type))
275 .or_service_specific_exception(-1)?;
276
277 Ok(())
278 }
279
280 /// Creates or update the idsig file by digesting the input APK file.
createOrUpdateIdsigFile( &self, input_fd: &ParcelFileDescriptor, idsig_fd: &ParcelFileDescriptor, ) -> binder::Result<()>281 fn createOrUpdateIdsigFile(
282 &self,
283 input_fd: &ParcelFileDescriptor,
284 idsig_fd: &ParcelFileDescriptor,
285 ) -> binder::Result<()> {
286 check_manage_access()?;
287
288 create_or_update_idsig_file(input_fd, idsig_fd).or_service_specific_exception(-1)?;
289 Ok(())
290 }
291
292 /// Get a list of all currently running VMs. This method is only intended for debug purposes,
293 /// and as such is only permitted from the shell user.
debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>>294 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
295 // Delegate to the global service, including checking the debug permission.
296 GLOBAL_SERVICE.debugListVms()
297 }
298
299 /// Get a list of assignable device types.
getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>>300 fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
301 // Delegate to the global service, including checking the permission.
302 GLOBAL_SERVICE.getAssignableDevices()
303 }
304
305 /// Get a list of supported OSes.
getSupportedOSList(&self) -> binder::Result<Vec<String>>306 fn getSupportedOSList(&self) -> binder::Result<Vec<String>> {
307 Ok(Vec::from_iter(SUPPORTED_OS_NAMES.iter().cloned()))
308 }
309
310 /// Returns whether given feature is enabled
isFeatureEnabled(&self, feature: &str) -> binder::Result<bool>311 fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
312 check_manage_access()?;
313 Ok(avf_features::is_feature_enabled(feature))
314 }
315
enableTestAttestation(&self) -> binder::Result<()>316 fn enableTestAttestation(&self) -> binder::Result<()> {
317 GLOBAL_SERVICE.enableTestAttestation()
318 }
319
isRemoteAttestationSupported(&self) -> binder::Result<bool>320 fn isRemoteAttestationSupported(&self) -> binder::Result<bool> {
321 check_manage_access()?;
322 GLOBAL_SERVICE.isRemoteAttestationSupported()
323 }
324
isUpdatableVmSupported(&self) -> binder::Result<bool>325 fn isUpdatableVmSupported(&self) -> binder::Result<bool> {
326 // The response is specific to Microdroid. Updatable VMs are only possible if device
327 // supports Secretkeeper. Guest OS needs to use Secretkeeper based secrets. Microdroid does
328 // this, however other guest OSes may do things differently.
329 check_manage_access()?;
330 Ok(is_secretkeeper_supported())
331 }
332
removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()>333 fn removeVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
334 check_manage_access()?;
335 GLOBAL_SERVICE.removeVmInstance(instance_id)
336 }
337
claimVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()>338 fn claimVmInstance(&self, instance_id: &[u8; 64]) -> binder::Result<()> {
339 check_manage_access()?;
340 GLOBAL_SERVICE.claimVmInstance(instance_id)
341 }
342 }
343
344 impl VirtualizationService {
init() -> VirtualizationService345 pub fn init() -> VirtualizationService {
346 VirtualizationService::default()
347 }
348
create_vm_context( &self, requester_debug_pid: pid_t, ) -> binder::Result<(VmContext, Cid, PathBuf)>349 fn create_vm_context(
350 &self,
351 requester_debug_pid: pid_t,
352 ) -> binder::Result<(VmContext, Cid, PathBuf)> {
353 const NUM_ATTEMPTS: usize = 5;
354
355 for _ in 0..NUM_ATTEMPTS {
356 let vm_context = GLOBAL_SERVICE.allocateGlobalVmContext(requester_debug_pid)?;
357 let cid = vm_context.getCid()? as Cid;
358 let temp_dir: PathBuf = vm_context.getTemporaryDirectory()?.into();
359 let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
360
361 // Start VM service listening for connections from the new CID on port=CID.
362 let port = cid;
363 match RpcServer::new_vsock(service, cid, port) {
364 Ok(vm_server) => {
365 vm_server.start();
366 return Ok((VmContext::new(vm_context, vm_server), cid, temp_dir));
367 }
368 Err(err) => {
369 warn!("Could not start RpcServer on port {}: {}", port, err);
370 }
371 }
372 }
373 Err(anyhow!("Too many attempts to create VM context failed"))
374 .or_service_specific_exception(-1)
375 }
376
create_vm_internal( &self, config: &VirtualMachineConfig, console_out_fd: Option<&ParcelFileDescriptor>, console_in_fd: Option<&ParcelFileDescriptor>, log_fd: Option<&ParcelFileDescriptor>, is_protected: &mut bool, ) -> binder::Result<Strong<dyn IVirtualMachine>>377 fn create_vm_internal(
378 &self,
379 config: &VirtualMachineConfig,
380 console_out_fd: Option<&ParcelFileDescriptor>,
381 console_in_fd: Option<&ParcelFileDescriptor>,
382 log_fd: Option<&ParcelFileDescriptor>,
383 is_protected: &mut bool,
384 ) -> binder::Result<Strong<dyn IVirtualMachine>> {
385 let requester_uid = get_calling_uid();
386 let requester_debug_pid = get_calling_pid();
387
388 check_config_features(config)?;
389
390 // Allocating VM context checks the MANAGE_VIRTUAL_MACHINE permission.
391 let (vm_context, cid, temporary_directory) = self.create_vm_context(requester_debug_pid)?;
392
393 if is_custom_config(config) {
394 check_use_custom_virtual_machine()?;
395 }
396
397 let gdb_port = extract_gdb_port(config);
398
399 // Additional permission checks if caller request gdb.
400 if gdb_port.is_some() {
401 check_gdb_allowed(config)?;
402 }
403
404 let device_tree_overlay = maybe_create_device_tree_overlay(config, &temporary_directory)?;
405
406 let debug_config = DebugConfig::new(config);
407 let ramdump = if !uses_gki_kernel(config) && debug_config.is_ramdump_needed() {
408 Some(prepare_ramdump_file(&temporary_directory)?)
409 } else {
410 None
411 };
412
413 let state = &mut *self.state.lock().unwrap();
414 let console_out_fd =
415 clone_or_prepare_logger_fd(&debug_config, console_out_fd, format!("Console({})", cid))?;
416 let console_in_fd = console_in_fd.map(clone_file).transpose()?;
417 let log_fd = clone_or_prepare_logger_fd(&debug_config, log_fd, format!("Log({})", cid))?;
418
419 // Counter to generate unique IDs for temporary image files.
420 let mut next_temporary_image_id = 0;
421 // Files which are referred to from composite images. These must be mapped to the crosvm
422 // child process, and not closed before it is started.
423 let mut indirect_files = vec![];
424
425 let (is_app_config, config) = match config {
426 VirtualMachineConfig::RawConfig(config) => (false, BorrowedOrOwned::Borrowed(config)),
427 VirtualMachineConfig::AppConfig(config) => {
428 let config = load_app_config(config, &debug_config, &temporary_directory)
429 .or_service_specific_exception_with(-1, |e| {
430 *is_protected = config.protectedVm;
431 let message = format!("Failed to load app config: {:?}", e);
432 error!("{}", message);
433 message
434 })?;
435 (true, BorrowedOrOwned::Owned(config))
436 }
437 };
438 let config = config.as_ref();
439 *is_protected = config.protectedVm;
440
441 // Check if partition images are labeled incorrectly. This is to prevent random images
442 // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
443 // being loaded in a pVM. This applies to everything but the instance image in the raw
444 // config, and everything but the non-executable, generated partitions in the app
445 // config.
446 config
447 .disks
448 .iter()
449 .flat_map(|disk| disk.partitions.iter())
450 .filter(|partition| {
451 if is_app_config {
452 !is_safe_app_partition(&partition.label)
453 } else {
454 !is_safe_raw_partition(&partition.label)
455 }
456 })
457 .try_for_each(check_label_for_partition)
458 .or_service_specific_exception(-1)?;
459
460 // Check if files for payloads and bases are NOT coming from /vendor and /odm, as they may
461 // have unstable interfaces.
462 // TODO(b/316431494): remove once Treble interfaces are stabilized.
463 check_partitions_for_files(config).or_service_specific_exception(-1)?;
464
465 let kernel = maybe_clone_file(&config.kernel)?;
466 let initrd = maybe_clone_file(&config.initrd)?;
467
468 // In a protected VM, we require custom kernels to come from a trusted source (b/237054515).
469 if config.protectedVm {
470 check_label_for_kernel_files(&kernel, &initrd).or_service_specific_exception(-1)?;
471 }
472
473 let zero_filler_path = temporary_directory.join("zero.img");
474 write_zero_filler(&zero_filler_path)
475 .context("Failed to make composite image")
476 .with_log()
477 .or_service_specific_exception(-1)?;
478
479 // Assemble disk images if needed.
480 let disks = config
481 .disks
482 .iter()
483 .map(|disk| {
484 assemble_disk_image(
485 disk,
486 &zero_filler_path,
487 &temporary_directory,
488 &mut next_temporary_image_id,
489 &mut indirect_files,
490 )
491 })
492 .collect::<Result<Vec<DiskFile>, _>>()?;
493
494 let (cpus, host_cpu_topology) = match config.cpuTopology {
495 CpuTopology::MATCH_HOST => (None, true),
496 CpuTopology::ONE_CPU => (NonZeroU32::new(1), false),
497 val => {
498 return Err(anyhow!("Failed to parse CPU topology value {:?}", val))
499 .with_log()
500 .or_service_specific_exception(-1);
501 }
502 };
503
504 let (vfio_devices, dtbo) = if !config.devices.is_empty() {
505 let mut set = HashSet::new();
506 for device in config.devices.iter() {
507 let path = canonicalize(device)
508 .with_context(|| format!("can't canonicalize {device}"))
509 .or_service_specific_exception(-1)?;
510 if !set.insert(path) {
511 return Err(anyhow!("duplicated device {device}"))
512 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
513 }
514 }
515 let devices = GLOBAL_SERVICE.bindDevicesToVfioDriver(&config.devices)?;
516 let dtbo_file = File::from(
517 GLOBAL_SERVICE
518 .getDtboFile()?
519 .as_ref()
520 .try_clone()
521 .context("Failed to create VM DTBO from ParcelFileDescriptor")
522 .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
523 );
524 (devices, Some(dtbo_file))
525 } else {
526 (vec![], None)
527 };
528 let display_config = if cfg!(paravirtualized_devices) {
529 config
530 .displayConfig
531 .as_ref()
532 .map(DisplayConfig::new)
533 .transpose()
534 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
535 } else {
536 None
537 };
538 let gpu_config = if cfg!(paravirtualized_devices) {
539 config
540 .gpuConfig
541 .as_ref()
542 .map(GpuConfig::new)
543 .transpose()
544 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
545 } else {
546 None
547 };
548
549 let input_device_options = if cfg!(paravirtualized_devices) {
550 config
551 .inputDevices
552 .iter()
553 .map(to_input_device_option_from)
554 .collect::<Result<Vec<InputDeviceOption>, _>>()
555 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
556 } else {
557 vec![]
558 };
559
560 // Create TAP network interface if the VM supports network.
561 let tap = if cfg!(network) && config.networkSupported {
562 if *is_protected {
563 return Err(anyhow!("Network feature is not supported for pVM yet"))
564 .with_log()
565 .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION)?;
566 }
567 Some(File::from(
568 GLOBAL_SERVICE
569 .createTapInterface(&get_this_pid().to_string())?
570 .as_ref()
571 .try_clone()
572 .context("Failed to get TAP interface from ParcelFileDescriptor")
573 .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
574 ))
575 } else {
576 None
577 };
578 let virtio_snd_backend =
579 if cfg!(paravirtualized_devices) { Some(String::from("aaudio")) } else { None };
580
581 // Actually start the VM.
582 let crosvm_config = CrosvmConfig {
583 cid,
584 name: config.name.clone(),
585 bootloader: maybe_clone_file(&config.bootloader)?,
586 kernel,
587 initrd,
588 disks,
589 params: config.params.to_owned(),
590 protected: *is_protected,
591 debug_config,
592 memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
593 cpus,
594 host_cpu_topology,
595 console_out_fd,
596 console_in_fd,
597 log_fd,
598 ramdump,
599 indirect_files,
600 platform_version: parse_platform_version_req(&config.platformVersion)?,
601 detect_hangup: is_app_config,
602 gdb_port,
603 vfio_devices,
604 dtbo,
605 device_tree_overlay,
606 display_config,
607 input_device_options,
608 hugepages: config.hugePages,
609 tap,
610 virtio_snd_backend,
611 console_input_device: config.consoleInputDevice.clone(),
612 boost_uclamp: config.boostUclamp,
613 gpu_config,
614 };
615 let instance = Arc::new(
616 VmInstance::new(
617 crosvm_config,
618 temporary_directory,
619 requester_uid,
620 requester_debug_pid,
621 vm_context,
622 )
623 .with_context(|| format!("Failed to create VM with config {:?}", config))
624 .with_log()
625 .or_service_specific_exception(-1)?,
626 );
627 state.add_vm(Arc::downgrade(&instance));
628 Ok(VirtualMachine::create(instance))
629 }
630 }
631
632 /// Returns whether a VM config represents a "custom" virtual machine, which requires the
633 /// USE_CUSTOM_VIRTUAL_MACHINE.
is_custom_config(config: &VirtualMachineConfig) -> bool634 fn is_custom_config(config: &VirtualMachineConfig) -> bool {
635 match config {
636 // Any raw (non-Microdroid) VM is considered custom.
637 VirtualMachineConfig::RawConfig(_) => true,
638 VirtualMachineConfig::AppConfig(config) => {
639 // Some features are reserved for platform apps only, even when using
640 // VirtualMachineAppConfig. Almost all of these features are grouped in the
641 // CustomConfig struct:
642 // - controlling CPUs;
643 // - gdbPort is set, meaning that crosvm will start a gdb server;
644 // - using anything other than the default kernel;
645 // - specifying devices to be assigned.
646 if config.customConfig.is_some() {
647 true
648 } else {
649 // Additional custom features not included in CustomConfig:
650 // - specifying a config file;
651 // - specifying extra APKs;
652 // - specifying an OS other than Microdroid.
653 (match &config.payload {
654 Payload::ConfigPath(_) => true,
655 Payload::PayloadConfig(payload_config) => !payload_config.extraApks.is_empty(),
656 }) || config.osName != MICRODROID_OS_NAME
657 }
658 }
659 }
660 }
661
extract_vendor_hashtree_digest(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>>662 fn extract_vendor_hashtree_digest(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
663 let VirtualMachineConfig::AppConfig(config) = config else {
664 return Ok(None);
665 };
666 let Some(custom_config) = &config.customConfig else {
667 return Ok(None);
668 };
669 let Some(file) = custom_config.vendorImage.as_ref() else {
670 return Ok(None);
671 };
672
673 let file = clone_file(file)?;
674 let size =
675 file.metadata().context("Failed to get metadata from microdroid vendor image")?.len();
676 let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
677 .context("Failed to get vbmeta from microdroid-vendor.img")?;
678
679 for descriptor in vbmeta.descriptors()?.iter() {
680 if let vbmeta::Descriptor::Hashtree(_) = descriptor {
681 let root_digest = hex::encode(descriptor.to_hashtree()?.root_digest());
682 return Ok(Some(root_digest.as_bytes().to_vec()));
683 }
684 }
685 Err(anyhow!("No hashtree digest is extracted from microdroid vendor image"))
686 }
687
maybe_create_device_tree_overlay( config: &VirtualMachineConfig, temporary_directory: &Path, ) -> binder::Result<Option<File>>688 fn maybe_create_device_tree_overlay(
689 config: &VirtualMachineConfig,
690 temporary_directory: &Path,
691 ) -> binder::Result<Option<File>> {
692 // Currently, VirtMgr adds the host copy of reference DT & untrusted properties
693 // (e.g. instance-id)
694 let host_ref_dt = Path::new(VM_REFERENCE_DT_ON_HOST_PATH);
695 let host_ref_dt = if host_ref_dt.exists()
696 && read_dir(host_ref_dt).or_service_specific_exception(-1)?.next().is_some()
697 {
698 Some(host_ref_dt)
699 } else {
700 warn!("VM reference DT doesn't exist in host DT");
701 None
702 };
703
704 let vendor_hashtree_digest = extract_vendor_hashtree_digest(config)
705 .context("Failed to extract vendor hashtree digest")
706 .or_service_specific_exception(-1)?;
707
708 let trusted_props = if let Some(ref vendor_hashtree_digest) = vendor_hashtree_digest {
709 info!(
710 "Passing vendor hashtree digest to pvmfw. This will be rejected if it doesn't \
711 match the trusted digest in the pvmfw config, causing the VM to fail to start."
712 );
713 vec![(cstr!("vendor_hashtree_descriptor_root_digest"), vendor_hashtree_digest.as_slice())]
714 } else {
715 vec![]
716 };
717
718 let instance_id;
719 let mut untrusted_props = Vec::with_capacity(2);
720 if cfg!(llpvm_changes) {
721 instance_id = extract_instance_id(config);
722 untrusted_props.push((cstr!("instance-id"), &instance_id[..]));
723 let want_updatable = extract_want_updatable(config);
724 if want_updatable && is_secretkeeper_supported() {
725 // Let guest know that it can defer rollback protection to Secretkeeper by setting
726 // an empty property in untrusted node in DT. This enables Updatable VMs.
727 untrusted_props.push((cstr!("defer-rollback-protection"), &[]))
728 }
729 }
730
731 let device_tree_overlay = if host_ref_dt.is_some()
732 || !untrusted_props.is_empty()
733 || !trusted_props.is_empty()
734 {
735 let dt_output = temporary_directory.join(VM_DT_OVERLAY_PATH);
736 let mut data = [0_u8; VM_DT_OVERLAY_MAX_SIZE];
737 let fdt =
738 create_device_tree_overlay(&mut data, host_ref_dt, &untrusted_props, &trusted_props)
739 .map_err(|e| anyhow!("Failed to create DT overlay, {e:?}"))
740 .or_service_specific_exception(-1)?;
741 fs::write(&dt_output, fdt.as_slice()).or_service_specific_exception(-1)?;
742 Some(File::open(dt_output).or_service_specific_exception(-1)?)
743 } else {
744 None
745 };
746 Ok(device_tree_overlay)
747 }
748
write_zero_filler(zero_filler_path: &Path) -> Result<()>749 fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
750 let file = OpenOptions::new()
751 .create_new(true)
752 .read(true)
753 .write(true)
754 .open(zero_filler_path)
755 .with_context(|| "Failed to create zero.img")?;
756 file.set_len(ZERO_FILLER_SIZE)?;
757 Ok(())
758 }
759
format_as_android_vm_instance(part: &mut dyn Write) -> std::io::Result<()>760 fn format_as_android_vm_instance(part: &mut dyn Write) -> std::io::Result<()> {
761 part.write_all(ANDROID_VM_INSTANCE_MAGIC.as_bytes())?;
762 part.write_all(&ANDROID_VM_INSTANCE_VERSION.to_le_bytes())?;
763 part.flush()
764 }
765
format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()>766 fn format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()> {
767 part.write_all(UNFORMATTED_STORAGE_MAGIC.as_bytes())?;
768 part.flush()
769 }
770
round_up(input: u64, granularity: u64) -> u64771 fn round_up(input: u64, granularity: u64) -> u64 {
772 if granularity == 0 {
773 return input;
774 }
775 // If the input is absurdly large we round down instead of up; it's going to fail anyway.
776 let result = input.checked_add(granularity - 1).unwrap_or(input);
777 (result / granularity) * granularity
778 }
779
to_input_device_option_from(input_device: &InputDevice) -> Result<InputDeviceOption>780 fn to_input_device_option_from(input_device: &InputDevice) -> Result<InputDeviceOption> {
781 Ok(match input_device {
782 InputDevice::SingleTouch(single_touch) => InputDeviceOption::SingleTouch {
783 file: clone_file(single_touch.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?)?,
784 height: u32::try_from(single_touch.height)?,
785 width: u32::try_from(single_touch.width)?,
786 name: if !single_touch.name.is_empty() {
787 Some(single_touch.name.clone())
788 } else {
789 None
790 },
791 },
792 InputDevice::EvDev(evdev) => InputDeviceOption::EvDev(clone_file(
793 evdev.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
794 )?),
795 InputDevice::Keyboard(keyboard) => InputDeviceOption::Keyboard(clone_file(
796 keyboard.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
797 )?),
798 InputDevice::Mouse(mouse) => InputDeviceOption::Mouse(clone_file(
799 mouse.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
800 )?),
801 })
802 }
803 /// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
804 ///
805 /// This may involve assembling a composite disk from a set of partition images.
assemble_disk_image( disk: &DiskImage, zero_filler_path: &Path, temporary_directory: &Path, next_temporary_image_id: &mut u64, indirect_files: &mut Vec<File>, ) -> Result<DiskFile, Status>806 fn assemble_disk_image(
807 disk: &DiskImage,
808 zero_filler_path: &Path,
809 temporary_directory: &Path,
810 next_temporary_image_id: &mut u64,
811 indirect_files: &mut Vec<File>,
812 ) -> Result<DiskFile, Status> {
813 let image = if !disk.partitions.is_empty() {
814 if disk.image.is_some() {
815 warn!("DiskImage {:?} contains both image and partitions.", disk);
816 return Err(anyhow!("DiskImage contains both image and partitions"))
817 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
818 }
819
820 let composite_image_filenames =
821 make_composite_image_filenames(temporary_directory, next_temporary_image_id);
822 let (image, partition_files) = make_composite_image(
823 &disk.partitions,
824 zero_filler_path,
825 &composite_image_filenames.composite,
826 &composite_image_filenames.header,
827 &composite_image_filenames.footer,
828 )
829 .with_context(|| format!("Failed to make composite disk image with config {:?}", disk))
830 .with_log()
831 .or_service_specific_exception(-1)?;
832
833 // Pass the file descriptors for the various partition files to crosvm when it
834 // is run.
835 indirect_files.extend(partition_files);
836
837 image
838 } else if let Some(image) = &disk.image {
839 clone_file(image)?
840 } else {
841 warn!("DiskImage {:?} didn't contain image or partitions.", disk);
842 return Err(anyhow!("DiskImage didn't contain image or partitions."))
843 .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
844 };
845
846 Ok(DiskFile { image, writable: disk.writable })
847 }
848
append_kernel_param(param: &str, vm_config: &mut VirtualMachineRawConfig)849 fn append_kernel_param(param: &str, vm_config: &mut VirtualMachineRawConfig) {
850 if let Some(ref mut params) = vm_config.params {
851 params.push(' ');
852 params.push_str(param)
853 } else {
854 vm_config.params = Some(param.to_owned())
855 }
856 }
857
extract_os_name_from_config_path(config: &Path) -> Option<String>858 fn extract_os_name_from_config_path(config: &Path) -> Option<String> {
859 if config.extension()?.to_str()? != "json" {
860 return None;
861 }
862
863 Some(config.with_extension("").file_name()?.to_str()?.to_owned())
864 }
865
extract_os_names_from_configs(config_glob_pattern: &str) -> Result<HashSet<String>>866 fn extract_os_names_from_configs(config_glob_pattern: &str) -> Result<HashSet<String>> {
867 let configs = glob(config_glob_pattern)?.collect::<Result<Vec<_>, _>>()?;
868 let os_names =
869 configs.iter().filter_map(|x| extract_os_name_from_config_path(x)).collect::<HashSet<_>>();
870
871 Ok(os_names)
872 }
873
get_supported_os_names() -> Result<HashSet<String>>874 fn get_supported_os_names() -> Result<HashSet<String>> {
875 if !cfg!(vendor_modules) {
876 return Ok(iter::once(MICRODROID_OS_NAME.to_owned()).collect());
877 }
878
879 extract_os_names_from_configs("/apex/com.android.virt/etc/microdroid*.json")
880 }
881
is_valid_os(os_name: &str) -> bool882 fn is_valid_os(os_name: &str) -> bool {
883 SUPPORTED_OS_NAMES.contains(os_name)
884 }
885
uses_gki_kernel(config: &VirtualMachineConfig) -> bool886 fn uses_gki_kernel(config: &VirtualMachineConfig) -> bool {
887 if !cfg!(vendor_modules) {
888 return false;
889 }
890 match config {
891 VirtualMachineConfig::RawConfig(_) => false,
892 VirtualMachineConfig::AppConfig(config) => config.osName.starts_with("microdroid_gki-"),
893 }
894 }
895
load_app_config( config: &VirtualMachineAppConfig, debug_config: &DebugConfig, temporary_directory: &Path, ) -> Result<VirtualMachineRawConfig>896 fn load_app_config(
897 config: &VirtualMachineAppConfig,
898 debug_config: &DebugConfig,
899 temporary_directory: &Path,
900 ) -> Result<VirtualMachineRawConfig> {
901 let apk_file = clone_file(config.apk.as_ref().unwrap())?;
902 let idsig_file = clone_file(config.idsig.as_ref().unwrap())?;
903 let instance_file = clone_file(config.instanceImage.as_ref().unwrap())?;
904
905 let storage_image = if let Some(file) = config.encryptedStorageImage.as_ref() {
906 Some(clone_file(file)?)
907 } else {
908 None
909 };
910
911 let vm_payload_config;
912 let extra_apk_files: Vec<_>;
913 match &config.payload {
914 Payload::ConfigPath(config_path) => {
915 vm_payload_config =
916 load_vm_payload_config_from_file(&apk_file, config_path.as_str())
917 .with_context(|| format!("Couldn't read config from {}", config_path))?;
918 extra_apk_files = vm_payload_config
919 .extra_apks
920 .iter()
921 .enumerate()
922 .map(|(i, apk)| {
923 File::open(PathBuf::from(&apk.path))
924 .with_context(|| format!("Failed to open extra apk #{i} {}", apk.path))
925 })
926 .collect::<Result<_>>()?;
927 }
928 Payload::PayloadConfig(payload_config) => {
929 vm_payload_config = create_vm_payload_config(payload_config)?;
930 extra_apk_files =
931 payload_config.extraApks.iter().map(clone_file).collect::<binder::Result<_>>()?;
932 }
933 };
934
935 let payload_config_os = vm_payload_config.os.name.as_str();
936 if !payload_config_os.is_empty() && payload_config_os != "microdroid" {
937 bail!("'os' in payload config is deprecated");
938 }
939
940 // For now, the only supported OS is Microdroid and Microdroid GKI
941 let os_name = config.osName.as_str();
942 if !is_valid_os(os_name) {
943 bail!("Unknown OS \"{}\"", os_name);
944 }
945
946 // It is safe to construct a filename based on the os_name because we've already checked that it
947 // is one of the allowed values.
948 let vm_config_path = PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name));
949 let vm_config_file = File::open(vm_config_path)?;
950 let mut vm_config = VmConfig::load(&vm_config_file)?.to_parcelable()?;
951
952 if let Some(custom_config) = &config.customConfig {
953 if let Some(file) = custom_config.customKernelImage.as_ref() {
954 vm_config.kernel = Some(ParcelFileDescriptor::new(clone_file(file)?))
955 }
956 vm_config.gdbPort = custom_config.gdbPort;
957
958 if let Some(file) = custom_config.vendorImage.as_ref() {
959 add_microdroid_vendor_image(clone_file(file)?, &mut vm_config);
960 append_kernel_param("androidboot.microdroid.mount_vendor=1", &mut vm_config)
961 }
962
963 vm_config.devices.clone_from(&custom_config.devices);
964 vm_config.networkSupported = custom_config.networkSupported;
965 }
966
967 if config.memoryMib > 0 {
968 vm_config.memoryMib = config.memoryMib;
969 }
970
971 vm_config.name.clone_from(&config.name);
972 vm_config.protectedVm = config.protectedVm;
973 vm_config.cpuTopology = config.cpuTopology;
974 vm_config.hugePages = config.hugePages || vm_payload_config.hugepages;
975 vm_config.boostUclamp = config.boostUclamp;
976
977 // Microdroid takes additional init ramdisk & (optionally) storage image
978 add_microdroid_system_images(config, instance_file, storage_image, os_name, &mut vm_config)?;
979
980 // Include Microdroid payload disk (contains apks, idsigs) in vm config
981 add_microdroid_payload_images(
982 config,
983 debug_config,
984 temporary_directory,
985 apk_file,
986 idsig_file,
987 extra_apk_files,
988 &vm_payload_config,
989 &mut vm_config,
990 )?;
991
992 Ok(vm_config)
993 }
994
check_partition_for_file(fd: &ParcelFileDescriptor) -> Result<()>995 fn check_partition_for_file(fd: &ParcelFileDescriptor) -> Result<()> {
996 let path = format!("/proc/self/fd/{}", fd.as_raw_fd());
997 let link = fs::read_link(&path).context(format!("can't read_link {path}"))?;
998
999 // microdroid vendor image is OK
1000 if cfg!(vendor_modules) && link == Path::new("/vendor/etc/avf/microdroid/microdroid_vendor.img")
1001 {
1002 return Ok(());
1003 }
1004
1005 if link.starts_with("/vendor") || link.starts_with("/odm") {
1006 bail!("vendor or odm file {} can't be used for VM", link.display());
1007 }
1008
1009 Ok(())
1010 }
1011
check_partitions_for_files(config: &VirtualMachineRawConfig) -> Result<()>1012 fn check_partitions_for_files(config: &VirtualMachineRawConfig) -> Result<()> {
1013 config
1014 .disks
1015 .iter()
1016 .flat_map(|disk| disk.partitions.iter())
1017 .filter_map(|partition| partition.image.as_ref())
1018 .try_for_each(check_partition_for_file)?;
1019
1020 config.kernel.as_ref().map_or(Ok(()), check_partition_for_file)?;
1021 config.initrd.as_ref().map_or(Ok(()), check_partition_for_file)?;
1022 config.bootloader.as_ref().map_or(Ok(()), check_partition_for_file)?;
1023
1024 Ok(())
1025 }
1026
load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig>1027 fn load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig> {
1028 let mut apk_zip = ZipArchive::new(apk_file)?;
1029 let config_file = apk_zip.by_name(config_path)?;
1030 Ok(serde_json::from_reader(config_file)?)
1031 }
1032
create_vm_payload_config( payload_config: &VirtualMachinePayloadConfig, ) -> Result<VmPayloadConfig>1033 fn create_vm_payload_config(
1034 payload_config: &VirtualMachinePayloadConfig,
1035 ) -> Result<VmPayloadConfig> {
1036 // There isn't an actual config file. Construct a synthetic VmPayloadConfig from the explicit
1037 // parameters we've been given. Microdroid will do something equivalent inside the VM using the
1038 // payload config that we send it via the metadata file.
1039
1040 let payload_binary_name = &payload_config.payloadBinaryName;
1041 if payload_binary_name.contains('/') {
1042 bail!("Payload binary name must not specify a path: {payload_binary_name}");
1043 }
1044
1045 let task = Task { type_: TaskType::MicrodroidLauncher, command: payload_binary_name.clone() };
1046
1047 // The VM only cares about how many there are, these names are actually ignored.
1048 let extra_apk_count = payload_config.extraApks.len();
1049 let extra_apks =
1050 (0..extra_apk_count).map(|i| ApkConfig { path: format!("extra-apk-{i}") }).collect();
1051
1052 Ok(VmPayloadConfig { task: Some(task), extra_apks, ..Default::default() })
1053 }
1054
1055 /// Generates a unique filename to use for a composite disk image.
make_composite_image_filenames( temporary_directory: &Path, next_temporary_image_id: &mut u64, ) -> CompositeImageFilenames1056 fn make_composite_image_filenames(
1057 temporary_directory: &Path,
1058 next_temporary_image_id: &mut u64,
1059 ) -> CompositeImageFilenames {
1060 let id = *next_temporary_image_id;
1061 *next_temporary_image_id += 1;
1062 CompositeImageFilenames {
1063 composite: temporary_directory.join(format!("composite-{}.img", id)),
1064 header: temporary_directory.join(format!("composite-{}-header.img", id)),
1065 footer: temporary_directory.join(format!("composite-{}-footer.img", id)),
1066 }
1067 }
1068
1069 /// Filenames for a composite disk image, including header and footer partitions.
1070 #[derive(Clone, Debug, Eq, PartialEq)]
1071 struct CompositeImageFilenames {
1072 /// The composite disk image itself.
1073 composite: PathBuf,
1074 /// The header partition image.
1075 header: PathBuf,
1076 /// The footer partition image.
1077 footer: PathBuf,
1078 }
1079
1080 /// Checks whether the caller has a specific permission
check_permission(perm: &str) -> binder::Result<()>1081 fn check_permission(perm: &str) -> binder::Result<()> {
1082 let calling_pid = get_calling_pid();
1083 let calling_uid = get_calling_uid();
1084 // Root can do anything
1085 if calling_uid == 0 {
1086 return Ok(());
1087 }
1088 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
1089 binder::wait_for_interface("permission")?;
1090 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
1091 Ok(())
1092 } else {
1093 Err(anyhow!("does not have the {} permission", perm))
1094 .or_binder_exception(ExceptionCode::SECURITY)
1095 }
1096 }
1097
1098 /// Check whether the caller of the current Binder method is allowed to manage VMs
check_manage_access() -> binder::Result<()>1099 fn check_manage_access() -> binder::Result<()> {
1100 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
1101 }
1102
1103 /// Check whether the caller of the current Binder method is allowed to create custom VMs
check_use_custom_virtual_machine() -> binder::Result<()>1104 fn check_use_custom_virtual_machine() -> binder::Result<()> {
1105 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
1106 }
1107
1108 /// Return whether a partition is exempt from selinux label checks, because we know that it does
1109 /// not contain code and is likely to be generated in an app-writable directory.
is_safe_app_partition(label: &str) -> bool1110 fn is_safe_app_partition(label: &str) -> bool {
1111 // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
1112 label == "vm-instance"
1113 || label == "encryptedstore"
1114 || label == "microdroid-apk-idsig"
1115 || label == "payload-metadata"
1116 || label.starts_with("extra-idsig-")
1117 }
1118
1119 /// Returns whether a partition with the given label is safe for a raw config VM.
is_safe_raw_partition(label: &str) -> bool1120 fn is_safe_raw_partition(label: &str) -> bool {
1121 label == "vm-instance"
1122 }
1123
1124 /// Check that a file SELinux label is acceptable.
1125 ///
1126 /// We only want to allow code in a VM to be sourced from places that apps, and the
1127 /// system or vendor, do not have write access to.
1128 ///
1129 /// Note that sepolicy must also grant read access for these types to both virtualization
1130 /// service and crosvm.
1131 ///
1132 /// App private data files are deliberately excluded, to avoid arbitrary payloads being run on
1133 /// user devices (W^X).
check_label_is_allowed(context: &SeContext) -> Result<()>1134 fn check_label_is_allowed(context: &SeContext) -> Result<()> {
1135 match context.selinux_type()? {
1136 | "apk_data_file" // APKs of an installed app
1137 | "shell_data_file" // test files created via adb shell
1138 | "staging_data_file" // updated/staged APEX images
1139 | "system_file" // immutable dm-verity protected partition
1140 | "virtualizationservice_data_file" // files created by VS / VirtMgr
1141 | "vendor_microdroid_file" // immutable dm-verity protected partition (/vendor/etc/avf/microdroid/.*)
1142 => Ok(()),
1143 _ => bail!("Label {} is not allowed", context),
1144 }
1145 }
1146
check_label_for_partition(partition: &Partition) -> Result<()>1147 fn check_label_for_partition(partition: &Partition) -> Result<()> {
1148 let file = partition.image.as_ref().unwrap().as_ref();
1149 check_label_is_allowed(&getfilecon(file)?)
1150 .with_context(|| format!("Partition {} invalid", &partition.label))
1151 }
1152
check_label_for_kernel_files(kernel: &Option<File>, initrd: &Option<File>) -> Result<()>1153 fn check_label_for_kernel_files(kernel: &Option<File>, initrd: &Option<File>) -> Result<()> {
1154 if let Some(f) = kernel {
1155 check_label_for_file(f, "kernel")?;
1156 }
1157 if let Some(f) = initrd {
1158 check_label_for_file(f, "initrd")?;
1159 }
1160 Ok(())
1161 }
check_label_for_file(file: &File, name: &str) -> Result<()>1162 fn check_label_for_file(file: &File, name: &str) -> Result<()> {
1163 check_label_is_allowed(&getfilecon(file)?).with_context(|| format!("{} file invalid", name))
1164 }
1165
1166 /// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
1167 #[derive(Debug)]
1168 struct VirtualMachine {
1169 instance: Arc<VmInstance>,
1170 }
1171
1172 impl VirtualMachine {
create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine>1173 fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
1174 BnVirtualMachine::new_binder(VirtualMachine { instance }, BinderFeatures::default())
1175 }
1176 }
1177
1178 impl Interface for VirtualMachine {}
1179
1180 impl IVirtualMachine for VirtualMachine {
getCid(&self) -> binder::Result<i32>1181 fn getCid(&self) -> binder::Result<i32> {
1182 // Don't check permission. The owner of the VM might have passed this binder object to
1183 // others.
1184 Ok(self.instance.cid as i32)
1185 }
1186
getState(&self) -> binder::Result<VirtualMachineState>1187 fn getState(&self) -> binder::Result<VirtualMachineState> {
1188 // Don't check permission. The owner of the VM might have passed this binder object to
1189 // others.
1190 Ok(get_state(&self.instance))
1191 }
1192
registerCallback( &self, callback: &Strong<dyn IVirtualMachineCallback>, ) -> binder::Result<()>1193 fn registerCallback(
1194 &self,
1195 callback: &Strong<dyn IVirtualMachineCallback>,
1196 ) -> binder::Result<()> {
1197 // Don't check permission. The owner of the VM might have passed this binder object to
1198 // others.
1199 //
1200 // TODO: Should this give an error if the VM is already dead?
1201 self.instance.callbacks.add(callback.clone());
1202 Ok(())
1203 }
1204
start(&self) -> binder::Result<()>1205 fn start(&self) -> binder::Result<()> {
1206 self.instance
1207 .start()
1208 .with_context(|| format!("Error starting VM with CID {}", self.instance.cid))
1209 .with_log()
1210 .or_service_specific_exception(-1)
1211 }
1212
stop(&self) -> binder::Result<()>1213 fn stop(&self) -> binder::Result<()> {
1214 self.instance
1215 .kill()
1216 .with_context(|| format!("Error stopping VM with CID {}", self.instance.cid))
1217 .with_log()
1218 .or_service_specific_exception(-1)
1219 }
1220
onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()>1221 fn onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()> {
1222 self.instance
1223 .trim_memory(level)
1224 .with_context(|| format!("Error trimming VM with CID {}", self.instance.cid))
1225 .with_log()
1226 .or_service_specific_exception(-1)
1227 }
1228
connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor>1229 fn connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor> {
1230 if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
1231 return Err(anyhow!("VM is not running")).or_service_specific_exception(-1);
1232 }
1233 let port = port as u32;
1234 if port < 1024 {
1235 return Err(anyhow!("Can't connect to privileged port {port}"))
1236 .or_service_specific_exception(-1);
1237 }
1238 let stream = VsockStream::connect_with_cid_port(self.instance.cid, port)
1239 .context("Failed to connect")
1240 .or_service_specific_exception(-1)?;
1241 Ok(vsock_stream_to_pfd(stream))
1242 }
1243 }
1244
1245 impl Drop for VirtualMachine {
drop(&mut self)1246 fn drop(&mut self) {
1247 debug!("Dropping {:?}", self);
1248 if let Err(e) = self.instance.kill() {
1249 debug!("Error stopping dropped VM with CID {}: {:?}", self.instance.cid, e);
1250 }
1251 }
1252 }
1253
1254 /// A set of Binders to be called back in response to various events on the VM, such as when it
1255 /// dies.
1256 #[derive(Debug, Default)]
1257 pub struct VirtualMachineCallbacks(Mutex<Vec<Strong<dyn IVirtualMachineCallback>>>);
1258
1259 impl VirtualMachineCallbacks {
1260 /// Call all registered callbacks to notify that the payload has started.
notify_payload_started(&self, cid: Cid)1261 pub fn notify_payload_started(&self, cid: Cid) {
1262 let callbacks = &*self.0.lock().unwrap();
1263 for callback in callbacks {
1264 if let Err(e) = callback.onPayloadStarted(cid as i32) {
1265 error!("Error notifying payload start event from VM CID {}: {:?}", cid, e);
1266 }
1267 }
1268 }
1269
1270 /// Call all registered callbacks to notify that the payload is ready to serve.
notify_payload_ready(&self, cid: Cid)1271 pub fn notify_payload_ready(&self, cid: Cid) {
1272 let callbacks = &*self.0.lock().unwrap();
1273 for callback in callbacks {
1274 if let Err(e) = callback.onPayloadReady(cid as i32) {
1275 error!("Error notifying payload ready event from VM CID {}: {:?}", cid, e);
1276 }
1277 }
1278 }
1279
1280 /// Call all registered callbacks to notify that the payload has finished.
notify_payload_finished(&self, cid: Cid, exit_code: i32)1281 pub fn notify_payload_finished(&self, cid: Cid, exit_code: i32) {
1282 let callbacks = &*self.0.lock().unwrap();
1283 for callback in callbacks {
1284 if let Err(e) = callback.onPayloadFinished(cid as i32, exit_code) {
1285 error!("Error notifying payload finish event from VM CID {}: {:?}", cid, e);
1286 }
1287 }
1288 }
1289
1290 /// Call all registered callbacks to say that the VM encountered an error.
notify_error(&self, cid: Cid, error_code: ErrorCode, message: &str)1291 pub fn notify_error(&self, cid: Cid, error_code: ErrorCode, message: &str) {
1292 let callbacks = &*self.0.lock().unwrap();
1293 for callback in callbacks {
1294 if let Err(e) = callback.onError(cid as i32, error_code, message) {
1295 error!("Error notifying error event from VM CID {}: {:?}", cid, e);
1296 }
1297 }
1298 }
1299
1300 /// Call all registered callbacks to say that the VM has died.
callback_on_died(&self, cid: Cid, reason: DeathReason)1301 pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
1302 let callbacks = &*self.0.lock().unwrap();
1303 for callback in callbacks {
1304 if let Err(e) = callback.onDied(cid as i32, reason) {
1305 error!("Error notifying exit of VM CID {}: {:?}", cid, e);
1306 }
1307 }
1308 }
1309
1310 /// Add a new callback to the set.
add(&self, callback: Strong<dyn IVirtualMachineCallback>)1311 fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
1312 self.0.lock().unwrap().push(callback);
1313 }
1314 }
1315
1316 /// The mutable state of the VirtualizationService. There should only be one instance of this
1317 /// struct.
1318 #[derive(Debug, Default)]
1319 struct State {
1320 /// The VMs which have been started. When VMs are started a weak reference is added to this
1321 /// list while a strong reference is returned to the caller over Binder. Once all copies of
1322 /// the Binder client are dropped the weak reference here will become invalid, and will be
1323 /// removed from the list opportunistically the next time `add_vm` is called.
1324 vms: Vec<Weak<VmInstance>>,
1325 }
1326
1327 impl State {
1328 /// Get a list of VMs which still have Binder references to them.
vms(&self) -> Vec<Arc<VmInstance>>1329 fn vms(&self) -> Vec<Arc<VmInstance>> {
1330 // Attempt to upgrade the weak pointers to strong pointers.
1331 self.vms.iter().filter_map(Weak::upgrade).collect()
1332 }
1333
1334 /// Add a new VM to the list.
add_vm(&mut self, vm: Weak<VmInstance>)1335 fn add_vm(&mut self, vm: Weak<VmInstance>) {
1336 // Garbage collect any entries from the stored list which no longer exist.
1337 self.vms.retain(|vm| vm.strong_count() > 0);
1338
1339 // Actually add the new VM.
1340 self.vms.push(vm);
1341 }
1342
1343 /// Get a VM that corresponds to the given cid
get_vm(&self, cid: Cid) -> Option<Arc<VmInstance>>1344 fn get_vm(&self, cid: Cid) -> Option<Arc<VmInstance>> {
1345 self.vms().into_iter().find(|vm| vm.cid == cid)
1346 }
1347 }
1348
1349 /// Gets the `VirtualMachineState` of the given `VmInstance`.
get_state(instance: &VmInstance) -> VirtualMachineState1350 fn get_state(instance: &VmInstance) -> VirtualMachineState {
1351 match &*instance.vm_state.lock().unwrap() {
1352 VmState::NotStarted { .. } => VirtualMachineState::NOT_STARTED,
1353 VmState::Running { .. } => match instance.payload_state() {
1354 PayloadState::Starting => VirtualMachineState::STARTING,
1355 PayloadState::Started => VirtualMachineState::STARTED,
1356 PayloadState::Ready => VirtualMachineState::READY,
1357 PayloadState::Finished => VirtualMachineState::FINISHED,
1358 PayloadState::Hangup => VirtualMachineState::DEAD,
1359 },
1360 VmState::Dead => VirtualMachineState::DEAD,
1361 VmState::Failed => VirtualMachineState::DEAD,
1362 }
1363 }
1364
1365 /// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
clone_file(file: &ParcelFileDescriptor) -> binder::Result<File>1366 pub fn clone_file(file: &ParcelFileDescriptor) -> binder::Result<File> {
1367 file.as_ref()
1368 .try_clone()
1369 .context("Failed to clone File from ParcelFileDescriptor")
1370 .or_binder_exception(ExceptionCode::BAD_PARCELABLE)
1371 .map(File::from)
1372 }
1373
1374 /// Converts an `&Option<ParcelFileDescriptor>` to an `Option<File>` by cloning the file.
maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> binder::Result<Option<File>>1375 fn maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> binder::Result<Option<File>> {
1376 file.as_ref().map(clone_file).transpose()
1377 }
1378
1379 /// Converts a `VsockStream` to a `ParcelFileDescriptor`.
vsock_stream_to_pfd(stream: VsockStream) -> ParcelFileDescriptor1380 fn vsock_stream_to_pfd(stream: VsockStream) -> ParcelFileDescriptor {
1381 // SAFETY: ownership is transferred from stream to f
1382 let f = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
1383 ParcelFileDescriptor::new(f)
1384 }
1385
1386 /// Parses the platform version requirement string.
parse_platform_version_req(s: &str) -> binder::Result<VersionReq>1387 fn parse_platform_version_req(s: &str) -> binder::Result<VersionReq> {
1388 VersionReq::parse(s)
1389 .with_context(|| format!("Invalid platform version requirement {}", s))
1390 .or_binder_exception(ExceptionCode::BAD_PARCELABLE)
1391 }
1392
1393 /// Create the empty ramdump file
prepare_ramdump_file(temporary_directory: &Path) -> binder::Result<File>1394 fn prepare_ramdump_file(temporary_directory: &Path) -> binder::Result<File> {
1395 // `ramdump_write` is sent to crosvm and will be the backing store for the /dev/hvc1 where
1396 // VM will emit ramdump to. `ramdump_read` will be sent back to the client (i.e. the VM
1397 // owner) for readout.
1398 let ramdump_path = temporary_directory.join("ramdump");
1399 let ramdump = File::create(ramdump_path)
1400 .context("Failed to prepare ramdump file")
1401 .with_log()
1402 .or_service_specific_exception(-1)?;
1403 Ok(ramdump)
1404 }
1405
is_protected(config: &VirtualMachineConfig) -> bool1406 fn is_protected(config: &VirtualMachineConfig) -> bool {
1407 match config {
1408 VirtualMachineConfig::RawConfig(config) => config.protectedVm,
1409 VirtualMachineConfig::AppConfig(config) => config.protectedVm,
1410 }
1411 }
1412
check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()>1413 fn check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()> {
1414 if is_protected(config) {
1415 return Err(anyhow!("Can't use gdb with protected VMs"))
1416 .or_binder_exception(ExceptionCode::SECURITY);
1417 }
1418
1419 if get_debug_level(config) == Some(DebugLevel::NONE) {
1420 return Err(anyhow!("Can't use gdb with non-debuggable VMs"))
1421 .or_binder_exception(ExceptionCode::SECURITY);
1422 }
1423
1424 Ok(())
1425 }
1426
extract_instance_id(config: &VirtualMachineConfig) -> [u8; 64]1427 fn extract_instance_id(config: &VirtualMachineConfig) -> [u8; 64] {
1428 match config {
1429 VirtualMachineConfig::RawConfig(config) => config.instanceId,
1430 VirtualMachineConfig::AppConfig(config) => config.instanceId,
1431 }
1432 }
1433
extract_want_updatable(config: &VirtualMachineConfig) -> bool1434 fn extract_want_updatable(config: &VirtualMachineConfig) -> bool {
1435 match config {
1436 VirtualMachineConfig::RawConfig(_) => true,
1437 VirtualMachineConfig::AppConfig(config) => {
1438 let Some(custom) = &config.customConfig else { return true };
1439 custom.wantUpdatable
1440 }
1441 }
1442 }
1443
extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16>1444 fn extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16> {
1445 match config {
1446 VirtualMachineConfig::RawConfig(config) => NonZeroU16::new(config.gdbPort as u16),
1447 VirtualMachineConfig::AppConfig(config) => {
1448 NonZeroU16::new(config.customConfig.as_ref().map(|c| c.gdbPort).unwrap_or(0) as u16)
1449 }
1450 }
1451 }
1452
check_no_vendor_modules(config: &VirtualMachineConfig) -> binder::Result<()>1453 fn check_no_vendor_modules(config: &VirtualMachineConfig) -> binder::Result<()> {
1454 let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
1455 if let Some(custom_config) = &config.customConfig {
1456 if custom_config.vendorImage.is_some() || custom_config.customKernelImage.is_some() {
1457 return Err(anyhow!("vendor modules feature is disabled"))
1458 .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
1459 }
1460 }
1461 Ok(())
1462 }
1463
check_no_devices(config: &VirtualMachineConfig) -> binder::Result<()>1464 fn check_no_devices(config: &VirtualMachineConfig) -> binder::Result<()> {
1465 let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
1466 if let Some(custom_config) = &config.customConfig {
1467 if !custom_config.devices.is_empty() {
1468 return Err(anyhow!("device assignment feature is disabled"))
1469 .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
1470 }
1471 }
1472 Ok(())
1473 }
1474
check_no_extra_apks(config: &VirtualMachineConfig) -> binder::Result<()>1475 fn check_no_extra_apks(config: &VirtualMachineConfig) -> binder::Result<()> {
1476 let VirtualMachineConfig::AppConfig(config) = config else { return Ok(()) };
1477 let Payload::PayloadConfig(payload_config) = &config.payload else { return Ok(()) };
1478 if !payload_config.extraApks.is_empty() {
1479 return Err(anyhow!("multi-tenant feature is disabled"))
1480 .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
1481 }
1482 Ok(())
1483 }
1484
check_config_features(config: &VirtualMachineConfig) -> binder::Result<()>1485 fn check_config_features(config: &VirtualMachineConfig) -> binder::Result<()> {
1486 if !cfg!(vendor_modules) {
1487 check_no_vendor_modules(config)?;
1488 }
1489 if !cfg!(device_assignment) {
1490 check_no_devices(config)?;
1491 }
1492 if !cfg!(multi_tenant) {
1493 check_no_extra_apks(config)?;
1494 }
1495 Ok(())
1496 }
1497
clone_or_prepare_logger_fd( debug_config: &DebugConfig, fd: Option<&ParcelFileDescriptor>, tag: String, ) -> Result<Option<File>, Status>1498 fn clone_or_prepare_logger_fd(
1499 debug_config: &DebugConfig,
1500 fd: Option<&ParcelFileDescriptor>,
1501 tag: String,
1502 ) -> Result<Option<File>, Status> {
1503 if let Some(fd) = fd {
1504 return Ok(Some(clone_file(fd)?));
1505 }
1506
1507 if !debug_config.should_prepare_console_output() {
1508 return Ok(None);
1509 };
1510
1511 let (read_fd, write_fd) =
1512 pipe().context("Failed to create pipe").or_service_specific_exception(-1)?;
1513
1514 let mut reader = BufReader::new(File::from(read_fd));
1515 let write_fd = File::from(write_fd);
1516
1517 std::thread::spawn(move || loop {
1518 let mut buf = vec![];
1519 match reader.read_until(b'\n', &mut buf) {
1520 Ok(0) => {
1521 // EOF
1522 return;
1523 }
1524 Ok(size) => {
1525 if buf[size - 1] == b'\n' {
1526 buf.pop();
1527 }
1528 info!("{}: {}", &tag, &String::from_utf8_lossy(&buf));
1529 }
1530 Err(e) => {
1531 error!("Could not read console pipe: {:?}", e);
1532 return;
1533 }
1534 };
1535 });
1536
1537 Ok(Some(write_fd))
1538 }
1539
1540 /// Simple utility for referencing Borrowed or Owned. Similar to std::borrow::Cow, but
1541 /// it doesn't require that T implements Clone.
1542 enum BorrowedOrOwned<'a, T> {
1543 Borrowed(&'a T),
1544 Owned(T),
1545 }
1546
1547 impl<'a, T> AsRef<T> for BorrowedOrOwned<'a, T> {
as_ref(&self) -> &T1548 fn as_ref(&self) -> &T {
1549 match self {
1550 Self::Borrowed(b) => b,
1551 Self::Owned(o) => o,
1552 }
1553 }
1554 }
1555
1556 /// Implementation of `IVirtualMachineService`, the entry point of the AIDL service.
1557 #[derive(Debug, Default)]
1558 struct VirtualMachineService {
1559 state: Arc<Mutex<State>>,
1560 cid: Cid,
1561 }
1562
1563 impl Interface for VirtualMachineService {}
1564
1565 impl IVirtualMachineService for VirtualMachineService {
notifyPayloadStarted(&self) -> binder::Result<()>1566 fn notifyPayloadStarted(&self) -> binder::Result<()> {
1567 let cid = self.cid;
1568 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1569 info!("VM with CID {} started payload", cid);
1570 vm.update_payload_state(PayloadState::Started)
1571 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
1572 vm.callbacks.notify_payload_started(cid);
1573
1574 let vm_start_timestamp = vm.vm_metric.lock().unwrap().start_timestamp;
1575 write_vm_booted_stats(vm.requester_uid as i32, &vm.name, vm_start_timestamp);
1576 Ok(())
1577 } else {
1578 error!("notifyPayloadStarted is called from an unknown CID {}", cid);
1579 Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
1580 }
1581 }
1582
notifyPayloadReady(&self) -> binder::Result<()>1583 fn notifyPayloadReady(&self) -> binder::Result<()> {
1584 let cid = self.cid;
1585 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1586 info!("VM with CID {} reported payload is ready", cid);
1587 vm.update_payload_state(PayloadState::Ready)
1588 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
1589 vm.callbacks.notify_payload_ready(cid);
1590 Ok(())
1591 } else {
1592 error!("notifyPayloadReady is called from an unknown CID {}", cid);
1593 Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
1594 }
1595 }
1596
notifyPayloadFinished(&self, exit_code: i32) -> binder::Result<()>1597 fn notifyPayloadFinished(&self, exit_code: i32) -> binder::Result<()> {
1598 let cid = self.cid;
1599 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1600 info!("VM with CID {} finished payload", cid);
1601 vm.update_payload_state(PayloadState::Finished)
1602 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
1603 vm.callbacks.notify_payload_finished(cid, exit_code);
1604 Ok(())
1605 } else {
1606 error!("notifyPayloadFinished is called from an unknown CID {}", cid);
1607 Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
1608 }
1609 }
1610
notifyError(&self, error_code: ErrorCode, message: &str) -> binder::Result<()>1611 fn notifyError(&self, error_code: ErrorCode, message: &str) -> binder::Result<()> {
1612 let cid = self.cid;
1613 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1614 info!("VM with CID {} encountered an error", cid);
1615 vm.update_payload_state(PayloadState::Finished)
1616 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
1617 vm.callbacks.notify_error(cid, error_code, message);
1618 Ok(())
1619 } else {
1620 error!("notifyError is called from an unknown CID {}", cid);
1621 Err(anyhow!("cannot find a VM with CID {}", cid)).or_service_specific_exception(-1)
1622 }
1623 }
1624
getSecretkeeper(&self) -> binder::Result<Strong<dyn ISecretkeeper>>1625 fn getSecretkeeper(&self) -> binder::Result<Strong<dyn ISecretkeeper>> {
1626 if !is_secretkeeper_supported() {
1627 return Err(StatusCode::NAME_NOT_FOUND)?;
1628 }
1629 let sk = binder::wait_for_interface(SECRETKEEPER_IDENTIFIER)?;
1630 Ok(BnSecretkeeper::new_binder(SecretkeeperProxy(sk), BinderFeatures::default()))
1631 }
1632
requestAttestation(&self, csr: &[u8], test_mode: bool) -> binder::Result<Vec<Certificate>>1633 fn requestAttestation(&self, csr: &[u8], test_mode: bool) -> binder::Result<Vec<Certificate>> {
1634 GLOBAL_SERVICE.requestAttestation(csr, get_calling_uid() as i32, test_mode)
1635 }
1636 }
1637
is_secretkeeper_supported() -> bool1638 fn is_secretkeeper_supported() -> bool {
1639 binder::is_declared(SECRETKEEPER_IDENTIFIER)
1640 .expect("Could not check for declared Secretkeeper interface")
1641 }
1642
1643 impl VirtualMachineService {
new_binder(state: Arc<Mutex<State>>, cid: Cid) -> Strong<dyn IVirtualMachineService>1644 fn new_binder(state: Arc<Mutex<State>>, cid: Cid) -> Strong<dyn IVirtualMachineService> {
1645 BnVirtualMachineService::new_binder(
1646 VirtualMachineService { state, cid },
1647 BinderFeatures::default(),
1648 )
1649 }
1650 }
1651
1652 struct SecretkeeperProxy(Strong<dyn ISecretkeeper>);
1653
1654 impl Interface for SecretkeeperProxy {}
1655
1656 impl ISecretkeeper for SecretkeeperProxy {
processSecretManagementRequest(&self, req: &[u8]) -> binder::Result<Vec<u8>>1657 fn processSecretManagementRequest(&self, req: &[u8]) -> binder::Result<Vec<u8>> {
1658 // Pass the request to the channel, and read the response.
1659 self.0.processSecretManagementRequest(req)
1660 }
1661
getAuthGraphKe(&self) -> binder::Result<Strong<dyn IAuthGraphKeyExchange>>1662 fn getAuthGraphKe(&self) -> binder::Result<Strong<dyn IAuthGraphKeyExchange>> {
1663 let ag = AuthGraphKeyExchangeProxy(self.0.getAuthGraphKe()?);
1664 Ok(BnAuthGraphKeyExchange::new_binder(ag, BinderFeatures::default()))
1665 }
1666
deleteIds(&self, ids: &[SecretId]) -> binder::Result<()>1667 fn deleteIds(&self, ids: &[SecretId]) -> binder::Result<()> {
1668 self.0.deleteIds(ids)
1669 }
1670
deleteAll(&self) -> binder::Result<()>1671 fn deleteAll(&self) -> binder::Result<()> {
1672 self.0.deleteAll()
1673 }
1674 }
1675
1676 struct AuthGraphKeyExchangeProxy(Strong<dyn IAuthGraphKeyExchange>);
1677
1678 impl Interface for AuthGraphKeyExchangeProxy {}
1679
1680 impl IAuthGraphKeyExchange for AuthGraphKeyExchangeProxy {
create(&self) -> binder::Result<SessionInitiationInfo>1681 fn create(&self) -> binder::Result<SessionInitiationInfo> {
1682 self.0.create()
1683 }
1684
init( &self, peer_pub_key: &PubKey, peer_id: &Identity, peer_nonce: &[u8], peer_version: i32, ) -> binder::Result<KeInitResult>1685 fn init(
1686 &self,
1687 peer_pub_key: &PubKey,
1688 peer_id: &Identity,
1689 peer_nonce: &[u8],
1690 peer_version: i32,
1691 ) -> binder::Result<KeInitResult> {
1692 self.0.init(peer_pub_key, peer_id, peer_nonce, peer_version)
1693 }
1694
finish( &self, peer_pub_key: &PubKey, peer_id: &Identity, peer_signature: &SessionIdSignature, peer_nonce: &[u8], peer_version: i32, own_key: &Key, ) -> binder::Result<SessionInfo>1695 fn finish(
1696 &self,
1697 peer_pub_key: &PubKey,
1698 peer_id: &Identity,
1699 peer_signature: &SessionIdSignature,
1700 peer_nonce: &[u8],
1701 peer_version: i32,
1702 own_key: &Key,
1703 ) -> binder::Result<SessionInfo> {
1704 self.0.finish(peer_pub_key, peer_id, peer_signature, peer_nonce, peer_version, own_key)
1705 }
1706
authenticationComplete( &self, peer_signature: &SessionIdSignature, shared_keys: &[AuthgraphArc; 2], ) -> binder::Result<[AuthgraphArc; 2]>1707 fn authenticationComplete(
1708 &self,
1709 peer_signature: &SessionIdSignature,
1710 shared_keys: &[AuthgraphArc; 2],
1711 ) -> binder::Result<[AuthgraphArc; 2]> {
1712 self.0.authenticationComplete(peer_signature, shared_keys)
1713 }
1714 }
1715
1716 #[cfg(test)]
1717 mod tests {
1718 use super::*;
1719
1720 #[test]
test_is_allowed_label_for_partition() -> Result<()>1721 fn test_is_allowed_label_for_partition() -> Result<()> {
1722 let expected_results = vec![
1723 ("u:object_r:system_file:s0", true),
1724 ("u:object_r:apk_data_file:s0", true),
1725 ("u:object_r:app_data_file:s0", false),
1726 ("u:object_r:app_data_file:s0:c512,c768", false),
1727 ("u:object_r:privapp_data_file:s0:c512,c768", false),
1728 ("invalid", false),
1729 ("user:role:apk_data_file:severity:categories", true),
1730 ("user:role:apk_data_file:severity:categories:extraneous", false),
1731 ];
1732
1733 for (label, expected_valid) in expected_results {
1734 let context = SeContext::new(label)?;
1735 let result = check_label_is_allowed(&context);
1736 if expected_valid {
1737 assert!(result.is_ok(), "Expected label {} to be allowed, got {:?}", label, result);
1738 } else if result.is_ok() {
1739 bail!("Expected label {} to be disallowed", label);
1740 }
1741 }
1742 Ok(())
1743 }
1744
1745 #[test]
test_create_or_update_idsig_file_empty_apk() -> Result<()>1746 fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
1747 let apk = tempfile::tempfile().unwrap();
1748 let idsig = tempfile::tempfile().unwrap();
1749
1750 let ret = create_or_update_idsig_file(
1751 &ParcelFileDescriptor::new(apk),
1752 &ParcelFileDescriptor::new(idsig),
1753 );
1754 assert!(ret.is_err(), "should fail");
1755 Ok(())
1756 }
1757
1758 #[test]
test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()>1759 fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
1760 let tmp_dir = tempfile::TempDir::new().unwrap();
1761 let apk = File::open(tmp_dir.path()).unwrap();
1762 let idsig = tempfile::tempfile().unwrap();
1763
1764 let ret = create_or_update_idsig_file(
1765 &ParcelFileDescriptor::new(apk),
1766 &ParcelFileDescriptor::new(idsig),
1767 );
1768 assert!(ret.is_err(), "should fail");
1769 Ok(())
1770 }
1771
1772 /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
1773 /// on ext4 filesystem is passed.
1774 /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
1775 /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
1776 /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
1777 #[test]
test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()>1778 fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
1779 // APEXes are backed by the ext4.
1780 let apk = File::open("/apex/com.android.virt/").unwrap();
1781 let idsig = tempfile::tempfile().unwrap();
1782
1783 let ret = create_or_update_idsig_file(
1784 &ParcelFileDescriptor::new(apk),
1785 &ParcelFileDescriptor::new(idsig),
1786 );
1787 assert!(ret.is_err(), "should fail");
1788 Ok(())
1789 }
1790
1791 #[test]
test_create_or_update_idsig_does_not_update_if_already_valid() -> Result<()>1792 fn test_create_or_update_idsig_does_not_update_if_already_valid() -> Result<()> {
1793 use std::io::Seek;
1794
1795 // Pick any APK
1796 let mut apk = File::open("/system/priv-app/Shell/Shell.apk").unwrap();
1797 let mut idsig = tempfile::tempfile().unwrap();
1798
1799 create_or_update_idsig_file(
1800 &ParcelFileDescriptor::new(apk.try_clone()?),
1801 &ParcelFileDescriptor::new(idsig.try_clone()?),
1802 )?;
1803 let modified_orig = idsig.metadata()?.modified()?;
1804 apk.rewind()?;
1805 idsig.rewind()?;
1806
1807 // Call the function again
1808 create_or_update_idsig_file(
1809 &ParcelFileDescriptor::new(apk.try_clone()?),
1810 &ParcelFileDescriptor::new(idsig.try_clone()?),
1811 )?;
1812 let modified_new = idsig.metadata()?.modified()?;
1813 assert!(modified_orig == modified_new, "idsig file was updated unnecessarily");
1814 Ok(())
1815 }
1816
1817 #[test]
test_create_or_update_idsig_on_non_empty_file() -> Result<()>1818 fn test_create_or_update_idsig_on_non_empty_file() -> Result<()> {
1819 use std::io::Read;
1820
1821 // Pick any APK
1822 let mut apk = File::open("/system/priv-app/Shell/Shell.apk").unwrap();
1823 let idsig_empty = tempfile::tempfile().unwrap();
1824 let mut idsig_invalid = tempfile::tempfile().unwrap();
1825 idsig_invalid.write_all(b"Oops")?;
1826
1827 // Create new idsig
1828 create_or_update_idsig_file(
1829 &ParcelFileDescriptor::new(apk.try_clone()?),
1830 &ParcelFileDescriptor::new(idsig_empty.try_clone()?),
1831 )?;
1832 apk.rewind()?;
1833
1834 // Update idsig_invalid
1835 create_or_update_idsig_file(
1836 &ParcelFileDescriptor::new(apk.try_clone()?),
1837 &ParcelFileDescriptor::new(idsig_invalid.try_clone()?),
1838 )?;
1839
1840 // Ensure the 2 idsig files have same size!
1841 assert!(
1842 idsig_empty.metadata()?.len() == idsig_invalid.metadata()?.len(),
1843 "idsig files differ in size"
1844 );
1845 // Ensure the 2 idsig files have same content!
1846 for (b1, b2) in idsig_empty.bytes().zip(idsig_invalid.bytes()) {
1847 assert!(b1.unwrap() == b2.unwrap(), "idsig files differ")
1848 }
1849 Ok(())
1850 }
1851 #[test]
test_append_kernel_param_first_param()1852 fn test_append_kernel_param_first_param() {
1853 let mut vm_config = VirtualMachineRawConfig { ..Default::default() };
1854 append_kernel_param("foo=1", &mut vm_config);
1855 assert_eq!(vm_config.params, Some("foo=1".to_owned()))
1856 }
1857
1858 #[test]
test_append_kernel_param()1859 fn test_append_kernel_param() {
1860 let mut vm_config =
1861 VirtualMachineRawConfig { params: Some("foo=5".to_owned()), ..Default::default() };
1862 append_kernel_param("bar=42", &mut vm_config);
1863 assert_eq!(vm_config.params, Some("foo=5 bar=42".to_owned()))
1864 }
1865
test_extract_os_name_from_config_path( path: &Path, expected_result: Option<&str>, ) -> Result<()>1866 fn test_extract_os_name_from_config_path(
1867 path: &Path,
1868 expected_result: Option<&str>,
1869 ) -> Result<()> {
1870 let result = extract_os_name_from_config_path(path);
1871 if result.as_deref() != expected_result {
1872 bail!("Expected {:?} but was {:?}", expected_result, &result)
1873 }
1874 Ok(())
1875 }
1876
1877 #[test]
test_extract_os_name_from_microdroid_config() -> Result<()>1878 fn test_extract_os_name_from_microdroid_config() -> Result<()> {
1879 test_extract_os_name_from_config_path(
1880 Path::new("/apex/com.android.virt/etc/microdroid.json"),
1881 Some("microdroid"),
1882 )
1883 }
1884
1885 #[test]
test_extract_os_name_from_microdroid_gki_config() -> Result<()>1886 fn test_extract_os_name_from_microdroid_gki_config() -> Result<()> {
1887 test_extract_os_name_from_config_path(
1888 Path::new("/apex/com.android.virt/etc/microdroid_gki-android14-6.1.json"),
1889 Some("microdroid_gki-android14-6.1"),
1890 )
1891 }
1892
1893 #[test]
test_extract_os_name_from_invalid_path() -> Result<()>1894 fn test_extract_os_name_from_invalid_path() -> Result<()> {
1895 test_extract_os_name_from_config_path(
1896 Path::new("/apex/com.android.virt/etc/microdroid.img"),
1897 None,
1898 )
1899 }
1900
1901 #[test]
test_extract_os_name_from_configs() -> Result<()>1902 fn test_extract_os_name_from_configs() -> Result<()> {
1903 let tmp_dir = tempfile::TempDir::new()?;
1904 let tmp_dir_path = tmp_dir.path().to_owned();
1905
1906 let mut os_names: HashSet<String> = HashSet::new();
1907 os_names.insert("microdroid".to_owned());
1908 os_names.insert("microdroid_gki-android14-6.1".to_owned());
1909 os_names.insert("microdroid_gki-android15-6.1".to_owned());
1910
1911 // config files
1912 for os_name in &os_names {
1913 std::fs::write(tmp_dir_path.join(os_name.to_owned() + ".json"), b"")?;
1914 }
1915
1916 // fake files not related to configs
1917 std::fs::write(tmp_dir_path.join("microdroid_super.img"), b"")?;
1918 std::fs::write(tmp_dir_path.join("microdroid_foobar.apk"), b"")?;
1919
1920 let glob_pattern = match tmp_dir_path.join("microdroid*.json").to_str() {
1921 Some(s) => s.to_owned(),
1922 None => bail!("tmp_dir_path {:?} is not UTF-8", tmp_dir_path),
1923 };
1924
1925 let result = extract_os_names_from_configs(&glob_pattern)?;
1926 if result != os_names {
1927 bail!("Expected {:?} but was {:?}", os_names, result);
1928 }
1929 Ok(())
1930 }
1931 }
1932