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};
18 use crate::atom::{
19 write_vm_booted_stats, write_vm_creation_stats};
20 use crate::composite::make_composite_image;
21 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
22 use crate::debug_config::DebugConfig;
23 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
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 DeathReason::DeathReason,
28 ErrorCode::ErrorCode,
29 };
30 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
31 CpuTopology::CpuTopology,
32 DiskImage::DiskImage,
33 IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
34 IVirtualMachineCallback::IVirtualMachineCallback,
35 IVirtualizationService::IVirtualizationService,
36 MemoryTrimLevel::MemoryTrimLevel,
37 Partition::Partition,
38 PartitionType::PartitionType,
39 VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
40 VirtualMachineConfig::VirtualMachineConfig,
41 VirtualMachineDebugInfo::VirtualMachineDebugInfo,
42 VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
43 VirtualMachineRawConfig::VirtualMachineRawConfig,
44 VirtualMachineState::VirtualMachineState,
45 };
46 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal::IVirtualizationServiceInternal;
47 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
48 BnVirtualMachineService, IVirtualMachineService,
49 };
50 use anyhow::{anyhow, bail, Context, Result};
51 use apkverify::{HashAlgorithm, V4Signature};
52 use binder::{
53 self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor,
54 Status, StatusCode, Strong,
55 };
56 use disk::QcowFile;
57 use lazy_static::lazy_static;
58 use log::{debug, error, info, warn};
59 use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
60 use nix::unistd::pipe;
61 use rpcbinder::RpcServer;
62 use rustutils::system_properties;
63 use semver::VersionReq;
64 use std::convert::TryInto;
65 use std::ffi::CStr;
66 use std::fs::{read_dir, remove_file, File, OpenOptions};
67 use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
68 use std::num::{NonZeroU16, NonZeroU32};
69 use std::os::unix::io::{FromRawFd, IntoRawFd};
70 use std::os::unix::raw::pid_t;
71 use std::path::{Path, PathBuf};
72 use std::sync::{Arc, Mutex, Weak};
73 use vmconfig::VmConfig;
74 use vsock::VsockStream;
75 use zip::ZipArchive;
76
77 /// The unique ID of a VM used (together with a port number) for vsock communication.
78 pub type Cid = u32;
79
80 pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
81
82 /// The size of zero.img.
83 /// Gaps in composite disk images are filled with a shared zero.img.
84 const ZERO_FILLER_SIZE: u64 = 4096;
85
86 /// Magic string for the instance image
87 const ANDROID_VM_INSTANCE_MAGIC: &str = "Android-VM-instance";
88
89 /// Version of the instance image format
90 const ANDROID_VM_INSTANCE_VERSION: u16 = 1;
91
92 const MICRODROID_OS_NAME: &str = "microdroid";
93
94 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
95
96 /// crosvm requires all partitions to be a multiple of 4KiB.
97 const PARTITION_GRANULARITY_BYTES: u64 = 4096;
98
99 lazy_static! {
100 pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
101 wait_for_interface(BINDER_SERVICE_IDENTIFIER)
102 .expect("Could not connect to VirtualizationServiceInternal");
103 }
104
create_or_update_idsig_file( input_fd: &ParcelFileDescriptor, idsig_fd: &ParcelFileDescriptor, ) -> Result<()>105 fn create_or_update_idsig_file(
106 input_fd: &ParcelFileDescriptor,
107 idsig_fd: &ParcelFileDescriptor,
108 ) -> Result<()> {
109 let mut input = clone_file(input_fd)?;
110 let metadata = input.metadata().context("failed to get input metadata")?;
111 if !metadata.is_file() {
112 bail!("input is not a regular file");
113 }
114 let mut sig =
115 V4Signature::create(&mut input, get_current_sdk()?, 4096, &[], HashAlgorithm::SHA256)
116 .context("failed to create idsig")?;
117
118 let mut output = clone_file(idsig_fd)?;
119 output.set_len(0).context("failed to set_len on the idsig output")?;
120 sig.write_into(&mut output).context("failed to write idsig")?;
121 Ok(())
122 }
123
get_current_sdk() -> Result<u32>124 fn get_current_sdk() -> Result<u32> {
125 let current_sdk = system_properties::read("ro.build.version.sdk")?;
126 let current_sdk = current_sdk.ok_or_else(|| anyhow!("SDK version missing"))?;
127 current_sdk.parse().context("Malformed SDK version")
128 }
129
remove_temporary_files(path: &PathBuf) -> Result<()>130 pub fn remove_temporary_files(path: &PathBuf) -> Result<()> {
131 for dir_entry in read_dir(path)? {
132 remove_file(dir_entry?.path())?;
133 }
134 Ok(())
135 }
136
137 /// Implementation of `IVirtualizationService`, the entry point of the AIDL service.
138 #[derive(Debug, Default)]
139 pub struct VirtualizationService {
140 state: Arc<Mutex<State>>,
141 }
142
143 impl Interface for VirtualizationService {
dump(&self, mut file: &File, _args: &[&CStr]) -> Result<(), StatusCode>144 fn dump(&self, mut file: &File, _args: &[&CStr]) -> Result<(), StatusCode> {
145 check_permission("android.permission.DUMP").or(Err(StatusCode::PERMISSION_DENIED))?;
146 let state = &mut *self.state.lock().unwrap();
147 let vms = state.vms();
148 writeln!(file, "Running {0} VMs:", vms.len()).or(Err(StatusCode::UNKNOWN_ERROR))?;
149 for vm in vms {
150 writeln!(file, "VM CID: {}", vm.cid).or(Err(StatusCode::UNKNOWN_ERROR))?;
151 writeln!(file, "\tState: {:?}", vm.vm_state.lock().unwrap())
152 .or(Err(StatusCode::UNKNOWN_ERROR))?;
153 writeln!(file, "\tPayload state {:?}", vm.payload_state())
154 .or(Err(StatusCode::UNKNOWN_ERROR))?;
155 writeln!(file, "\tProtected: {}", vm.protected).or(Err(StatusCode::UNKNOWN_ERROR))?;
156 writeln!(file, "\ttemporary_directory: {}", vm.temporary_directory.to_string_lossy())
157 .or(Err(StatusCode::UNKNOWN_ERROR))?;
158 writeln!(file, "\trequester_uid: {}", vm.requester_uid)
159 .or(Err(StatusCode::UNKNOWN_ERROR))?;
160 writeln!(file, "\trequester_debug_pid: {}", vm.requester_debug_pid)
161 .or(Err(StatusCode::UNKNOWN_ERROR))?;
162 }
163 Ok(())
164 }
165 }
166
167 impl IVirtualizationService for VirtualizationService {
168 /// Creates (but does not start) a new VM with the given configuration, assigning it the next
169 /// available CID.
170 ///
171 /// Returns a binder `IVirtualMachine` object referring to it, as a handle for the client.
createVm( &self, config: &VirtualMachineConfig, console_fd: Option<&ParcelFileDescriptor>, log_fd: Option<&ParcelFileDescriptor>, ) -> binder::Result<Strong<dyn IVirtualMachine>>172 fn createVm(
173 &self,
174 config: &VirtualMachineConfig,
175 console_fd: Option<&ParcelFileDescriptor>,
176 log_fd: Option<&ParcelFileDescriptor>,
177 ) -> binder::Result<Strong<dyn IVirtualMachine>> {
178 let mut is_protected = false;
179 let ret = self.create_vm_internal(config, console_fd, log_fd, &mut is_protected);
180 write_vm_creation_stats(config, is_protected, &ret);
181 ret
182 }
183
184 /// 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<()>185 fn initializeWritablePartition(
186 &self,
187 image_fd: &ParcelFileDescriptor,
188 size_bytes: i64,
189 partition_type: PartitionType,
190 ) -> binder::Result<()> {
191 check_manage_access()?;
192 let size_bytes = size_bytes.try_into().map_err(|e| {
193 Status::new_exception_str(
194 ExceptionCode::ILLEGAL_ARGUMENT,
195 Some(format!("Invalid size {}: {:?}", size_bytes, e)),
196 )
197 })?;
198 let size_bytes = round_up(size_bytes, PARTITION_GRANULARITY_BYTES);
199 let image = clone_file(image_fd)?;
200 // initialize the file. Any data in the file will be erased.
201 image.set_len(0).map_err(|e| {
202 Status::new_service_specific_error_str(
203 -1,
204 Some(format!("Failed to reset a file: {:?}", e)),
205 )
206 })?;
207 let mut part = QcowFile::new(image, size_bytes).map_err(|e| {
208 Status::new_service_specific_error_str(
209 -1,
210 Some(format!("Failed to create QCOW2 image: {:?}", e)),
211 )
212 })?;
213
214 match partition_type {
215 PartitionType::RAW => Ok(()),
216 PartitionType::ANDROID_VM_INSTANCE => format_as_android_vm_instance(&mut part),
217 PartitionType::ENCRYPTEDSTORE => format_as_encryptedstore(&mut part),
218 _ => Err(Error::new(
219 ErrorKind::Unsupported,
220 format!("Unsupported partition type {:?}", partition_type),
221 )),
222 }
223 .map_err(|e| {
224 Status::new_service_specific_error_str(
225 -1,
226 Some(format!("Failed to initialize partition as {:?}: {:?}", partition_type, e)),
227 )
228 })?;
229
230 Ok(())
231 }
232
233 /// Creates or update the idsig file by digesting the input APK file.
createOrUpdateIdsigFile( &self, input_fd: &ParcelFileDescriptor, idsig_fd: &ParcelFileDescriptor, ) -> binder::Result<()>234 fn createOrUpdateIdsigFile(
235 &self,
236 input_fd: &ParcelFileDescriptor,
237 idsig_fd: &ParcelFileDescriptor,
238 ) -> binder::Result<()> {
239 // TODO(b/193504400): do this only when (1) idsig_fd is empty or (2) the APK digest in
240 // idsig_fd is different from APK digest in input_fd
241
242 check_manage_access()?;
243
244 create_or_update_idsig_file(input_fd, idsig_fd)
245 .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
246 Ok(())
247 }
248
249 /// Get a list of all currently running VMs. This method is only intended for debug purposes,
250 /// and as such is only permitted from the shell user.
debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>>251 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
252 // Delegate to the global service, including checking the debug permission.
253 GLOBAL_SERVICE.debugListVms()
254 }
255 }
256
257 impl VirtualizationService {
init() -> VirtualizationService258 pub fn init() -> VirtualizationService {
259 VirtualizationService::default()
260 }
261
create_vm_context( &self, requester_debug_pid: pid_t, ) -> binder::Result<(VmContext, Cid, PathBuf)>262 fn create_vm_context(
263 &self,
264 requester_debug_pid: pid_t,
265 ) -> binder::Result<(VmContext, Cid, PathBuf)> {
266 const NUM_ATTEMPTS: usize = 5;
267
268 for _ in 0..NUM_ATTEMPTS {
269 let vm_context = GLOBAL_SERVICE.allocateGlobalVmContext(requester_debug_pid)?;
270 let cid = vm_context.getCid()? as Cid;
271 let temp_dir: PathBuf = vm_context.getTemporaryDirectory()?.into();
272 let service = VirtualMachineService::new_binder(self.state.clone(), cid).as_binder();
273
274 // Start VM service listening for connections from the new CID on port=CID.
275 let port = cid;
276 match RpcServer::new_vsock(service, cid, port) {
277 Ok(vm_server) => {
278 vm_server.start();
279 return Ok((VmContext::new(vm_context, vm_server), cid, temp_dir));
280 }
281 Err(err) => {
282 warn!("Could not start RpcServer on port {}: {}", port, err);
283 }
284 }
285 }
286 Err(Status::new_service_specific_error_str(
287 -1,
288 Some("Too many attempts to create VM context failed."),
289 ))
290 }
291
create_vm_internal( &self, config: &VirtualMachineConfig, console_fd: Option<&ParcelFileDescriptor>, log_fd: Option<&ParcelFileDescriptor>, is_protected: &mut bool, ) -> binder::Result<Strong<dyn IVirtualMachine>>292 fn create_vm_internal(
293 &self,
294 config: &VirtualMachineConfig,
295 console_fd: Option<&ParcelFileDescriptor>,
296 log_fd: Option<&ParcelFileDescriptor>,
297 is_protected: &mut bool,
298 ) -> binder::Result<Strong<dyn IVirtualMachine>> {
299 let requester_uid = get_calling_uid();
300 let requester_debug_pid = get_calling_pid();
301
302 // Allocating VM context checks the MANAGE_VIRTUAL_MACHINE permission.
303 let (vm_context, cid, temporary_directory) = self.create_vm_context(requester_debug_pid)?;
304
305 let is_custom = match config {
306 VirtualMachineConfig::RawConfig(_) => true,
307 VirtualMachineConfig::AppConfig(config) => {
308 // Some features are reserved for platform apps only, even when using
309 // VirtualMachineAppConfig:
310 // - controlling CPUs;
311 // - specifying a config file in the APK;
312 // - gdbPort is set, meaning that crosvm will start a gdb server.
313 !config.taskProfiles.is_empty()
314 || matches!(config.payload, Payload::ConfigPath(_))
315 || config.gdbPort > 0
316 }
317 };
318 if is_custom {
319 check_use_custom_virtual_machine()?;
320 }
321
322 let gdb_port = extract_gdb_port(config);
323
324 // Additional permission checks if caller request gdb.
325 if gdb_port.is_some() {
326 check_gdb_allowed(config)?;
327 }
328
329 let debug_level = match config {
330 VirtualMachineConfig::AppConfig(config) => config.debugLevel,
331 _ => DebugLevel::NONE,
332 };
333 let debug_config = DebugConfig::new(debug_level);
334
335 let ramdump = if debug_config.is_ramdump_needed() {
336 Some(prepare_ramdump_file(&temporary_directory)?)
337 } else {
338 None
339 };
340
341 let state = &mut *self.state.lock().unwrap();
342 let console_fd =
343 clone_or_prepare_logger_fd(&debug_config, console_fd, format!("Console({})", cid))?;
344 let log_fd = clone_or_prepare_logger_fd(&debug_config, log_fd, format!("Log({})", cid))?;
345
346 // Counter to generate unique IDs for temporary image files.
347 let mut next_temporary_image_id = 0;
348 // Files which are referred to from composite images. These must be mapped to the crosvm
349 // child process, and not closed before it is started.
350 let mut indirect_files = vec![];
351
352 let (is_app_config, config) = match config {
353 VirtualMachineConfig::RawConfig(config) => (false, BorrowedOrOwned::Borrowed(config)),
354 VirtualMachineConfig::AppConfig(config) => {
355 let config =
356 load_app_config(config, &debug_config, &temporary_directory).map_err(|e| {
357 *is_protected = config.protectedVm;
358 let message = format!("Failed to load app config: {:?}", e);
359 error!("{}", message);
360 Status::new_service_specific_error_str(-1, Some(message))
361 })?;
362 (true, BorrowedOrOwned::Owned(config))
363 }
364 };
365 let config = config.as_ref();
366 *is_protected = config.protectedVm;
367
368 // Check if partition images are labeled incorrectly. This is to prevent random images
369 // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
370 // being loaded in a pVM. This applies to everything in the raw config, and everything but
371 // the non-executable, generated partitions in the app config.
372 config
373 .disks
374 .iter()
375 .flat_map(|disk| disk.partitions.iter())
376 .filter(|partition| {
377 if is_app_config {
378 !is_safe_app_partition(&partition.label)
379 } else {
380 true // all partitions are checked
381 }
382 })
383 .try_for_each(check_label_for_partition)
384 .map_err(|e| Status::new_service_specific_error_str(-1, Some(format!("{:?}", e))))?;
385
386 let kernel = maybe_clone_file(&config.kernel)?;
387 let initrd = maybe_clone_file(&config.initrd)?;
388
389 // In a protected VM, we require custom kernels to come from a trusted source (b/237054515).
390 if config.protectedVm {
391 check_label_for_kernel_files(&kernel, &initrd).map_err(|e| {
392 Status::new_service_specific_error_str(-1, Some(format!("{:?}", e)))
393 })?;
394 }
395
396 let zero_filler_path = temporary_directory.join("zero.img");
397 write_zero_filler(&zero_filler_path).map_err(|e| {
398 error!("Failed to make composite image: {:?}", e);
399 Status::new_service_specific_error_str(
400 -1,
401 Some(format!("Failed to make composite image: {:?}", e)),
402 )
403 })?;
404
405 // Assemble disk images if needed.
406 let disks = config
407 .disks
408 .iter()
409 .map(|disk| {
410 assemble_disk_image(
411 disk,
412 &zero_filler_path,
413 &temporary_directory,
414 &mut next_temporary_image_id,
415 &mut indirect_files,
416 )
417 })
418 .collect::<Result<Vec<DiskFile>, _>>()?;
419
420 let (cpus, host_cpu_topology) = match config.cpuTopology {
421 CpuTopology::MATCH_HOST => (None, true),
422 CpuTopology::ONE_CPU => (NonZeroU32::new(1), false),
423 val => {
424 error!("Unexpected value of CPU topology: {:?}", val);
425 return Err(Status::new_service_specific_error_str(
426 -1,
427 Some(format!("Failed to parse CPU topology value: {:?}", val)),
428 ));
429 }
430 };
431
432 // Actually start the VM.
433 let crosvm_config = CrosvmConfig {
434 cid,
435 name: config.name.clone(),
436 bootloader: maybe_clone_file(&config.bootloader)?,
437 kernel,
438 initrd,
439 disks,
440 params: config.params.to_owned(),
441 protected: *is_protected,
442 debug_config,
443 memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
444 cpus,
445 host_cpu_topology,
446 task_profiles: config.taskProfiles.clone(),
447 console_fd,
448 log_fd,
449 ramdump,
450 indirect_files,
451 platform_version: parse_platform_version_req(&config.platformVersion)?,
452 detect_hangup: is_app_config,
453 gdb_port,
454 };
455 let instance = Arc::new(
456 VmInstance::new(
457 crosvm_config,
458 temporary_directory,
459 requester_uid,
460 requester_debug_pid,
461 vm_context,
462 )
463 .map_err(|e| {
464 error!("Failed to create VM with config {:?}: {:?}", config, e);
465 Status::new_service_specific_error_str(
466 -1,
467 Some(format!("Failed to create VM: {:?}", e)),
468 )
469 })?,
470 );
471 state.add_vm(Arc::downgrade(&instance));
472 Ok(VirtualMachine::create(instance))
473 }
474 }
475
write_zero_filler(zero_filler_path: &Path) -> Result<()>476 fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
477 let file = OpenOptions::new()
478 .create_new(true)
479 .read(true)
480 .write(true)
481 .open(zero_filler_path)
482 .with_context(|| "Failed to create zero.img")?;
483 file.set_len(ZERO_FILLER_SIZE)?;
484 Ok(())
485 }
486
format_as_android_vm_instance(part: &mut dyn Write) -> std::io::Result<()>487 fn format_as_android_vm_instance(part: &mut dyn Write) -> std::io::Result<()> {
488 part.write_all(ANDROID_VM_INSTANCE_MAGIC.as_bytes())?;
489 part.write_all(&ANDROID_VM_INSTANCE_VERSION.to_le_bytes())?;
490 part.flush()
491 }
492
format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()>493 fn format_as_encryptedstore(part: &mut dyn Write) -> std::io::Result<()> {
494 part.write_all(UNFORMATTED_STORAGE_MAGIC.as_bytes())?;
495 part.flush()
496 }
497
round_up(input: u64, granularity: u64) -> u64498 fn round_up(input: u64, granularity: u64) -> u64 {
499 if granularity == 0 {
500 return input;
501 }
502 // If the input is absurdly large we round down instead of up; it's going to fail anyway.
503 let result = input.checked_add(granularity - 1).unwrap_or(input);
504 (result / granularity) * granularity
505 }
506
507 /// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
508 ///
509 /// 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>510 fn assemble_disk_image(
511 disk: &DiskImage,
512 zero_filler_path: &Path,
513 temporary_directory: &Path,
514 next_temporary_image_id: &mut u64,
515 indirect_files: &mut Vec<File>,
516 ) -> Result<DiskFile, Status> {
517 let image = if !disk.partitions.is_empty() {
518 if disk.image.is_some() {
519 warn!("DiskImage {:?} contains both image and partitions.", disk);
520 return Err(Status::new_exception_str(
521 ExceptionCode::ILLEGAL_ARGUMENT,
522 Some("DiskImage contains both image and partitions."),
523 ));
524 }
525
526 let composite_image_filenames =
527 make_composite_image_filenames(temporary_directory, next_temporary_image_id);
528 let (image, partition_files) = make_composite_image(
529 &disk.partitions,
530 zero_filler_path,
531 &composite_image_filenames.composite,
532 &composite_image_filenames.header,
533 &composite_image_filenames.footer,
534 )
535 .map_err(|e| {
536 error!("Failed to make composite image with config {:?}: {:?}", disk, e);
537 Status::new_service_specific_error_str(
538 -1,
539 Some(format!("Failed to make composite image: {:?}", e)),
540 )
541 })?;
542
543 // Pass the file descriptors for the various partition files to crosvm when it
544 // is run.
545 indirect_files.extend(partition_files);
546
547 image
548 } else if let Some(image) = &disk.image {
549 clone_file(image)?
550 } else {
551 warn!("DiskImage {:?} didn't contain image or partitions.", disk);
552 return Err(Status::new_exception_str(
553 ExceptionCode::ILLEGAL_ARGUMENT,
554 Some("DiskImage didn't contain image or partitions."),
555 ));
556 };
557
558 Ok(DiskFile { image, writable: disk.writable })
559 }
560
load_app_config( config: &VirtualMachineAppConfig, debug_config: &DebugConfig, temporary_directory: &Path, ) -> Result<VirtualMachineRawConfig>561 fn load_app_config(
562 config: &VirtualMachineAppConfig,
563 debug_config: &DebugConfig,
564 temporary_directory: &Path,
565 ) -> Result<VirtualMachineRawConfig> {
566 let apk_file = clone_file(config.apk.as_ref().unwrap())?;
567 let idsig_file = clone_file(config.idsig.as_ref().unwrap())?;
568 let instance_file = clone_file(config.instanceImage.as_ref().unwrap())?;
569
570 let storage_image = if let Some(file) = config.encryptedStorageImage.as_ref() {
571 Some(clone_file(file)?)
572 } else {
573 None
574 };
575
576 let vm_payload_config = match &config.payload {
577 Payload::ConfigPath(config_path) => {
578 load_vm_payload_config_from_file(&apk_file, config_path.as_str())
579 .with_context(|| format!("Couldn't read config from {}", config_path))?
580 }
581 Payload::PayloadConfig(payload_config) => create_vm_payload_config(payload_config)?,
582 };
583
584 // For now, the only supported OS is Microdroid
585 let os_name = vm_payload_config.os.name.as_str();
586 if os_name != MICRODROID_OS_NAME {
587 bail!("Unknown OS \"{}\"", os_name);
588 }
589
590 // It is safe to construct a filename based on the os_name because we've already checked that it
591 // is one of the allowed values.
592 let vm_config_path = PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name));
593 let vm_config_file = File::open(vm_config_path)?;
594 let mut vm_config = VmConfig::load(&vm_config_file)?.to_parcelable()?;
595
596 if config.memoryMib > 0 {
597 vm_config.memoryMib = config.memoryMib;
598 }
599
600 vm_config.name = config.name.clone();
601 vm_config.protectedVm = config.protectedVm;
602 vm_config.cpuTopology = config.cpuTopology;
603 vm_config.taskProfiles = config.taskProfiles.clone();
604 vm_config.gdbPort = config.gdbPort;
605
606 // Microdroid takes additional init ramdisk & (optionally) storage image
607 add_microdroid_system_images(config, instance_file, storage_image, &mut vm_config)?;
608
609 // Include Microdroid payload disk (contains apks, idsigs) in vm config
610 add_microdroid_payload_images(
611 config,
612 debug_config,
613 temporary_directory,
614 apk_file,
615 idsig_file,
616 &vm_payload_config,
617 &mut vm_config,
618 )?;
619
620 Ok(vm_config)
621 }
622
load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig>623 fn load_vm_payload_config_from_file(apk_file: &File, config_path: &str) -> Result<VmPayloadConfig> {
624 let mut apk_zip = ZipArchive::new(apk_file)?;
625 let config_file = apk_zip.by_name(config_path)?;
626 Ok(serde_json::from_reader(config_file)?)
627 }
628
create_vm_payload_config( payload_config: &VirtualMachinePayloadConfig, ) -> Result<VmPayloadConfig>629 fn create_vm_payload_config(
630 payload_config: &VirtualMachinePayloadConfig,
631 ) -> Result<VmPayloadConfig> {
632 // There isn't an actual config file. Construct a synthetic VmPayloadConfig from the explicit
633 // parameters we've been given. Microdroid will do something equivalent inside the VM using the
634 // payload config that we send it via the metadata file.
635
636 let payload_binary_name = &payload_config.payloadBinaryName;
637 if payload_binary_name.contains('/') {
638 bail!("Payload binary name must not specify a path: {payload_binary_name}");
639 }
640
641 let task = Task { type_: TaskType::MicrodroidLauncher, command: payload_binary_name.clone() };
642 Ok(VmPayloadConfig {
643 os: OsConfig { name: MICRODROID_OS_NAME.to_owned() },
644 task: Some(task),
645 apexes: vec![],
646 extra_apks: vec![],
647 prefer_staged: false,
648 export_tombstones: None,
649 enable_authfs: false,
650 })
651 }
652
653 /// Generates a unique filename to use for a composite disk image.
make_composite_image_filenames( temporary_directory: &Path, next_temporary_image_id: &mut u64, ) -> CompositeImageFilenames654 fn make_composite_image_filenames(
655 temporary_directory: &Path,
656 next_temporary_image_id: &mut u64,
657 ) -> CompositeImageFilenames {
658 let id = *next_temporary_image_id;
659 *next_temporary_image_id += 1;
660 CompositeImageFilenames {
661 composite: temporary_directory.join(format!("composite-{}.img", id)),
662 header: temporary_directory.join(format!("composite-{}-header.img", id)),
663 footer: temporary_directory.join(format!("composite-{}-footer.img", id)),
664 }
665 }
666
667 /// Filenames for a composite disk image, including header and footer partitions.
668 #[derive(Clone, Debug, Eq, PartialEq)]
669 struct CompositeImageFilenames {
670 /// The composite disk image itself.
671 composite: PathBuf,
672 /// The header partition image.
673 header: PathBuf,
674 /// The footer partition image.
675 footer: PathBuf,
676 }
677
678 /// Checks whether the caller has a specific permission
check_permission(perm: &str) -> binder::Result<()>679 fn check_permission(perm: &str) -> binder::Result<()> {
680 let calling_pid = get_calling_pid();
681 let calling_uid = get_calling_uid();
682 // Root can do anything
683 if calling_uid == 0 {
684 return Ok(());
685 }
686 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
687 binder::get_interface("permission")?;
688 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
689 Ok(())
690 } else {
691 Err(Status::new_exception_str(
692 ExceptionCode::SECURITY,
693 Some(format!("does not have the {} permission", perm)),
694 ))
695 }
696 }
697
698 /// Check whether the caller of the current Binder method is allowed to manage VMs
check_manage_access() -> binder::Result<()>699 fn check_manage_access() -> binder::Result<()> {
700 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
701 }
702
703 /// Check whether the caller of the current Binder method is allowed to create custom VMs
check_use_custom_virtual_machine() -> binder::Result<()>704 fn check_use_custom_virtual_machine() -> binder::Result<()> {
705 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
706 }
707
708 /// Return whether a partition is exempt from selinux label checks, because we know that it does
709 /// not contain code and is likely to be generated in an app-writable directory.
is_safe_app_partition(label: &str) -> bool710 fn is_safe_app_partition(label: &str) -> bool {
711 // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
712 label == "vm-instance"
713 || label == "encryptedstore"
714 || label == "microdroid-apk-idsig"
715 || label == "payload-metadata"
716 || label.starts_with("extra-idsig-")
717 }
718
719 /// Check that a file SELinux label is acceptable.
720 ///
721 /// We only want to allow code in a VM to be sourced from places that apps, and the
722 /// system, do not have write access to.
723 ///
724 /// Note that sepolicy must also grant read access for these types to both virtualization
725 /// service and crosvm.
726 ///
727 /// App private data files are deliberately excluded, to avoid arbitrary payloads being run on
728 /// user devices (W^X).
check_label_is_allowed(context: &SeContext) -> Result<()>729 fn check_label_is_allowed(context: &SeContext) -> Result<()> {
730 match context.selinux_type()? {
731 | "apk_data_file" // APKs of an installed app
732 | "shell_data_file" // test files created via adb shell
733 | "staging_data_file" // updated/staged APEX images
734 | "system_file" // immutable dm-verity protected partition
735 | "virtualizationservice_data_file" // files created by VS / VirtMgr
736 => Ok(()),
737 _ => bail!("Label {} is not allowed", context),
738 }
739 }
740
check_label_for_partition(partition: &Partition) -> Result<()>741 fn check_label_for_partition(partition: &Partition) -> Result<()> {
742 let file = partition.image.as_ref().unwrap().as_ref();
743 check_label_is_allowed(&getfilecon(file)?)
744 .with_context(|| format!("Partition {} invalid", &partition.label))
745 }
746
check_label_for_kernel_files(kernel: &Option<File>, initrd: &Option<File>) -> Result<()>747 fn check_label_for_kernel_files(kernel: &Option<File>, initrd: &Option<File>) -> Result<()> {
748 if let Some(f) = kernel {
749 check_label_for_file(f, "kernel")?;
750 }
751 if let Some(f) = initrd {
752 check_label_for_file(f, "initrd")?;
753 }
754 Ok(())
755 }
check_label_for_file(file: &File, name: &str) -> Result<()>756 fn check_label_for_file(file: &File, name: &str) -> Result<()> {
757 check_label_is_allowed(&getfilecon(file)?).with_context(|| format!("{} file invalid", name))
758 }
759
760 /// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
761 #[derive(Debug)]
762 struct VirtualMachine {
763 instance: Arc<VmInstance>,
764 }
765
766 impl VirtualMachine {
create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine>767 fn create(instance: Arc<VmInstance>) -> Strong<dyn IVirtualMachine> {
768 BnVirtualMachine::new_binder(VirtualMachine { instance }, BinderFeatures::default())
769 }
770 }
771
772 impl Interface for VirtualMachine {}
773
774 impl IVirtualMachine for VirtualMachine {
getCid(&self) -> binder::Result<i32>775 fn getCid(&self) -> binder::Result<i32> {
776 // Don't check permission. The owner of the VM might have passed this binder object to
777 // others.
778 Ok(self.instance.cid as i32)
779 }
780
getState(&self) -> binder::Result<VirtualMachineState>781 fn getState(&self) -> binder::Result<VirtualMachineState> {
782 // Don't check permission. The owner of the VM might have passed this binder object to
783 // others.
784 Ok(get_state(&self.instance))
785 }
786
registerCallback( &self, callback: &Strong<dyn IVirtualMachineCallback>, ) -> binder::Result<()>787 fn registerCallback(
788 &self,
789 callback: &Strong<dyn IVirtualMachineCallback>,
790 ) -> binder::Result<()> {
791 // Don't check permission. The owner of the VM might have passed this binder object to
792 // others.
793 //
794 // TODO: Should this give an error if the VM is already dead?
795 self.instance.callbacks.add(callback.clone());
796 Ok(())
797 }
798
start(&self) -> binder::Result<()>799 fn start(&self) -> binder::Result<()> {
800 self.instance.start().map_err(|e| {
801 error!("Error starting VM with CID {}: {:?}", self.instance.cid, e);
802 Status::new_service_specific_error_str(-1, Some(e.to_string()))
803 })
804 }
805
stop(&self) -> binder::Result<()>806 fn stop(&self) -> binder::Result<()> {
807 self.instance.kill().map_err(|e| {
808 error!("Error stopping VM with CID {}: {:?}", self.instance.cid, e);
809 Status::new_service_specific_error_str(-1, Some(e.to_string()))
810 })
811 }
812
onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()>813 fn onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()> {
814 self.instance.trim_memory(level).map_err(|e| {
815 error!("Error trimming VM with CID {}: {:?}", self.instance.cid, e);
816 Status::new_service_specific_error_str(-1, Some(e.to_string()))
817 })
818 }
819
connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor>820 fn connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor> {
821 if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
822 return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
823 }
824 let port = port as u32;
825 if port < 1024 {
826 return Err(Status::new_service_specific_error_str(
827 -1,
828 Some(format!("Can't connect to privileged port {port}")),
829 ));
830 }
831 let stream = VsockStream::connect_with_cid_port(self.instance.cid, port).map_err(|e| {
832 Status::new_service_specific_error_str(-1, Some(format!("Failed to connect: {:?}", e)))
833 })?;
834 Ok(vsock_stream_to_pfd(stream))
835 }
836 }
837
838 impl Drop for VirtualMachine {
drop(&mut self)839 fn drop(&mut self) {
840 debug!("Dropping {:?}", self);
841 if let Err(e) = self.instance.kill() {
842 debug!("Error stopping dropped VM with CID {}: {:?}", self.instance.cid, e);
843 }
844 }
845 }
846
847 /// A set of Binders to be called back in response to various events on the VM, such as when it
848 /// dies.
849 #[derive(Debug, Default)]
850 pub struct VirtualMachineCallbacks(Mutex<Vec<Strong<dyn IVirtualMachineCallback>>>);
851
852 impl VirtualMachineCallbacks {
853 /// Call all registered callbacks to notify that the payload has started.
notify_payload_started(&self, cid: Cid)854 pub fn notify_payload_started(&self, cid: Cid) {
855 let callbacks = &*self.0.lock().unwrap();
856 for callback in callbacks {
857 if let Err(e) = callback.onPayloadStarted(cid as i32) {
858 error!("Error notifying payload start event from VM CID {}: {:?}", cid, e);
859 }
860 }
861 }
862
863 /// Call all registered callbacks to notify that the payload is ready to serve.
notify_payload_ready(&self, cid: Cid)864 pub fn notify_payload_ready(&self, cid: Cid) {
865 let callbacks = &*self.0.lock().unwrap();
866 for callback in callbacks {
867 if let Err(e) = callback.onPayloadReady(cid as i32) {
868 error!("Error notifying payload ready event from VM CID {}: {:?}", cid, e);
869 }
870 }
871 }
872
873 /// Call all registered callbacks to notify that the payload has finished.
notify_payload_finished(&self, cid: Cid, exit_code: i32)874 pub fn notify_payload_finished(&self, cid: Cid, exit_code: i32) {
875 let callbacks = &*self.0.lock().unwrap();
876 for callback in callbacks {
877 if let Err(e) = callback.onPayloadFinished(cid as i32, exit_code) {
878 error!("Error notifying payload finish event from VM CID {}: {:?}", cid, e);
879 }
880 }
881 }
882
883 /// Call all registered callbacks to say that the VM encountered an error.
notify_error(&self, cid: Cid, error_code: ErrorCode, message: &str)884 pub fn notify_error(&self, cid: Cid, error_code: ErrorCode, message: &str) {
885 let callbacks = &*self.0.lock().unwrap();
886 for callback in callbacks {
887 if let Err(e) = callback.onError(cid as i32, error_code, message) {
888 error!("Error notifying error event from VM CID {}: {:?}", cid, e);
889 }
890 }
891 }
892
893 /// Call all registered callbacks to say that the VM has died.
callback_on_died(&self, cid: Cid, reason: DeathReason)894 pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
895 let callbacks = &*self.0.lock().unwrap();
896 for callback in callbacks {
897 if let Err(e) = callback.onDied(cid as i32, reason) {
898 error!("Error notifying exit of VM CID {}: {:?}", cid, e);
899 }
900 }
901 }
902
903 /// Add a new callback to the set.
add(&self, callback: Strong<dyn IVirtualMachineCallback>)904 fn add(&self, callback: Strong<dyn IVirtualMachineCallback>) {
905 self.0.lock().unwrap().push(callback);
906 }
907 }
908
909 /// The mutable state of the VirtualizationService. There should only be one instance of this
910 /// struct.
911 #[derive(Debug, Default)]
912 struct State {
913 /// The VMs which have been started. When VMs are started a weak reference is added to this list
914 /// while a strong reference is returned to the caller over Binder. Once all copies of the
915 /// Binder client are dropped the weak reference here will become invalid, and will be removed
916 /// from the list opportunistically the next time `add_vm` is called.
917 vms: Vec<Weak<VmInstance>>,
918 }
919
920 impl State {
921 /// Get a list of VMs which still have Binder references to them.
vms(&self) -> Vec<Arc<VmInstance>>922 fn vms(&self) -> Vec<Arc<VmInstance>> {
923 // Attempt to upgrade the weak pointers to strong pointers.
924 self.vms.iter().filter_map(Weak::upgrade).collect()
925 }
926
927 /// Add a new VM to the list.
add_vm(&mut self, vm: Weak<VmInstance>)928 fn add_vm(&mut self, vm: Weak<VmInstance>) {
929 // Garbage collect any entries from the stored list which no longer exist.
930 self.vms.retain(|vm| vm.strong_count() > 0);
931
932 // Actually add the new VM.
933 self.vms.push(vm);
934 }
935
936 /// Get a VM that corresponds to the given cid
get_vm(&self, cid: Cid) -> Option<Arc<VmInstance>>937 fn get_vm(&self, cid: Cid) -> Option<Arc<VmInstance>> {
938 self.vms().into_iter().find(|vm| vm.cid == cid)
939 }
940 }
941
942 /// Gets the `VirtualMachineState` of the given `VmInstance`.
get_state(instance: &VmInstance) -> VirtualMachineState943 fn get_state(instance: &VmInstance) -> VirtualMachineState {
944 match &*instance.vm_state.lock().unwrap() {
945 VmState::NotStarted { .. } => VirtualMachineState::NOT_STARTED,
946 VmState::Running { .. } => match instance.payload_state() {
947 PayloadState::Starting => VirtualMachineState::STARTING,
948 PayloadState::Started => VirtualMachineState::STARTED,
949 PayloadState::Ready => VirtualMachineState::READY,
950 PayloadState::Finished => VirtualMachineState::FINISHED,
951 PayloadState::Hangup => VirtualMachineState::DEAD,
952 },
953 VmState::Dead => VirtualMachineState::DEAD,
954 VmState::Failed => VirtualMachineState::DEAD,
955 }
956 }
957
958 /// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
clone_file(file: &ParcelFileDescriptor) -> Result<File, Status>959 pub fn clone_file(file: &ParcelFileDescriptor) -> Result<File, Status> {
960 file.as_ref().try_clone().map_err(|e| {
961 Status::new_exception_str(
962 ExceptionCode::BAD_PARCELABLE,
963 Some(format!("Failed to clone File from ParcelFileDescriptor: {:?}", e)),
964 )
965 })
966 }
967
968 /// Converts an `&Option<ParcelFileDescriptor>` to an `Option<File>` by cloning the file.
maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> Result<Option<File>, Status>969 fn maybe_clone_file(file: &Option<ParcelFileDescriptor>) -> Result<Option<File>, Status> {
970 file.as_ref().map(clone_file).transpose()
971 }
972
973 /// Converts a `VsockStream` to a `ParcelFileDescriptor`.
vsock_stream_to_pfd(stream: VsockStream) -> ParcelFileDescriptor974 fn vsock_stream_to_pfd(stream: VsockStream) -> ParcelFileDescriptor {
975 // SAFETY: ownership is transferred from stream to f
976 let f = unsafe { File::from_raw_fd(stream.into_raw_fd()) };
977 ParcelFileDescriptor::new(f)
978 }
979
980 /// Parses the platform version requirement string.
parse_platform_version_req(s: &str) -> Result<VersionReq, Status>981 fn parse_platform_version_req(s: &str) -> Result<VersionReq, Status> {
982 VersionReq::parse(s).map_err(|e| {
983 Status::new_exception_str(
984 ExceptionCode::BAD_PARCELABLE,
985 Some(format!("Invalid platform version requirement {}: {:?}", s, e)),
986 )
987 })
988 }
989
990 /// Create the empty ramdump file
prepare_ramdump_file(temporary_directory: &Path) -> binder::Result<File>991 fn prepare_ramdump_file(temporary_directory: &Path) -> binder::Result<File> {
992 // `ramdump_write` is sent to crosvm and will be the backing store for the /dev/hvc1 where
993 // VM will emit ramdump to. `ramdump_read` will be sent back to the client (i.e. the VM
994 // owner) for readout.
995 let ramdump_path = temporary_directory.join("ramdump");
996 let ramdump = File::create(ramdump_path).map_err(|e| {
997 error!("Failed to prepare ramdump file: {:?}", e);
998 Status::new_service_specific_error_str(
999 -1,
1000 Some(format!("Failed to prepare ramdump file: {:?}", e)),
1001 )
1002 })?;
1003 Ok(ramdump)
1004 }
1005
is_protected(config: &VirtualMachineConfig) -> bool1006 fn is_protected(config: &VirtualMachineConfig) -> bool {
1007 match config {
1008 VirtualMachineConfig::RawConfig(config) => config.protectedVm,
1009 VirtualMachineConfig::AppConfig(config) => config.protectedVm,
1010 }
1011 }
1012
check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()>1013 fn check_gdb_allowed(config: &VirtualMachineConfig) -> binder::Result<()> {
1014 if is_protected(config) {
1015 return Err(Status::new_exception_str(
1016 ExceptionCode::SECURITY,
1017 Some("can't use gdb with protected VMs"),
1018 ));
1019 }
1020
1021 match config {
1022 VirtualMachineConfig::RawConfig(_) => Ok(()),
1023 VirtualMachineConfig::AppConfig(config) => {
1024 if config.debugLevel != DebugLevel::FULL {
1025 Err(Status::new_exception_str(
1026 ExceptionCode::SECURITY,
1027 Some("can't use gdb with non-debuggable VMs"),
1028 ))
1029 } else {
1030 Ok(())
1031 }
1032 }
1033 }
1034 }
1035
extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16>1036 fn extract_gdb_port(config: &VirtualMachineConfig) -> Option<NonZeroU16> {
1037 match config {
1038 VirtualMachineConfig::RawConfig(config) => NonZeroU16::new(config.gdbPort as u16),
1039 VirtualMachineConfig::AppConfig(config) => NonZeroU16::new(config.gdbPort as u16),
1040 }
1041 }
1042
clone_or_prepare_logger_fd( debug_config: &DebugConfig, fd: Option<&ParcelFileDescriptor>, tag: String, ) -> Result<Option<File>, Status>1043 fn clone_or_prepare_logger_fd(
1044 debug_config: &DebugConfig,
1045 fd: Option<&ParcelFileDescriptor>,
1046 tag: String,
1047 ) -> Result<Option<File>, Status> {
1048 if let Some(fd) = fd {
1049 return Ok(Some(clone_file(fd)?));
1050 }
1051
1052 if !debug_config.should_prepare_console_output() {
1053 return Ok(None);
1054 };
1055
1056 let (raw_read_fd, raw_write_fd) = pipe().map_err(|e| {
1057 Status::new_service_specific_error_str(-1, Some(format!("Failed to create pipe: {:?}", e)))
1058 })?;
1059
1060 // SAFETY: We are the sole owners of these fds as they were just created.
1061 let mut reader = BufReader::new(unsafe { File::from_raw_fd(raw_read_fd) });
1062 let write_fd = unsafe { File::from_raw_fd(raw_write_fd) };
1063
1064 std::thread::spawn(move || loop {
1065 let mut buf = vec![];
1066 match reader.read_until(b'\n', &mut buf) {
1067 Ok(0) => {
1068 // EOF
1069 return;
1070 }
1071 Ok(size) => {
1072 if buf[size - 1] == b'\n' {
1073 buf.pop();
1074 }
1075 info!("{}: {}", &tag, &String::from_utf8_lossy(&buf));
1076 }
1077 Err(e) => {
1078 error!("Could not read console pipe: {:?}", e);
1079 return;
1080 }
1081 };
1082 });
1083
1084 Ok(Some(write_fd))
1085 }
1086
1087 /// Simple utility for referencing Borrowed or Owned. Similar to std::borrow::Cow, but
1088 /// it doesn't require that T implements Clone.
1089 enum BorrowedOrOwned<'a, T> {
1090 Borrowed(&'a T),
1091 Owned(T),
1092 }
1093
1094 impl<'a, T> AsRef<T> for BorrowedOrOwned<'a, T> {
as_ref(&self) -> &T1095 fn as_ref(&self) -> &T {
1096 match self {
1097 Self::Borrowed(b) => b,
1098 Self::Owned(o) => o,
1099 }
1100 }
1101 }
1102
1103 /// Implementation of `IVirtualMachineService`, the entry point of the AIDL service.
1104 #[derive(Debug, Default)]
1105 struct VirtualMachineService {
1106 state: Arc<Mutex<State>>,
1107 cid: Cid,
1108 }
1109
1110 impl Interface for VirtualMachineService {}
1111
1112 impl IVirtualMachineService for VirtualMachineService {
notifyPayloadStarted(&self) -> binder::Result<()>1113 fn notifyPayloadStarted(&self) -> binder::Result<()> {
1114 let cid = self.cid;
1115 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1116 info!("VM with CID {} started payload", cid);
1117 vm.update_payload_state(PayloadState::Started).map_err(|e| {
1118 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
1119 })?;
1120 vm.callbacks.notify_payload_started(cid);
1121
1122 let vm_start_timestamp = vm.vm_metric.lock().unwrap().start_timestamp;
1123 write_vm_booted_stats(vm.requester_uid as i32, &vm.name, vm_start_timestamp);
1124 Ok(())
1125 } else {
1126 error!("notifyPayloadStarted is called from an unknown CID {}", cid);
1127 Err(Status::new_service_specific_error_str(
1128 -1,
1129 Some(format!("cannot find a VM with CID {}", cid)),
1130 ))
1131 }
1132 }
1133
notifyPayloadReady(&self) -> binder::Result<()>1134 fn notifyPayloadReady(&self) -> binder::Result<()> {
1135 let cid = self.cid;
1136 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1137 info!("VM with CID {} reported payload is ready", cid);
1138 vm.update_payload_state(PayloadState::Ready).map_err(|e| {
1139 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
1140 })?;
1141 vm.callbacks.notify_payload_ready(cid);
1142 Ok(())
1143 } else {
1144 error!("notifyPayloadReady is called from an unknown CID {}", cid);
1145 Err(Status::new_service_specific_error_str(
1146 -1,
1147 Some(format!("cannot find a VM with CID {}", cid)),
1148 ))
1149 }
1150 }
1151
notifyPayloadFinished(&self, exit_code: i32) -> binder::Result<()>1152 fn notifyPayloadFinished(&self, exit_code: i32) -> binder::Result<()> {
1153 let cid = self.cid;
1154 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1155 info!("VM with CID {} finished payload", cid);
1156 vm.update_payload_state(PayloadState::Finished).map_err(|e| {
1157 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
1158 })?;
1159 vm.callbacks.notify_payload_finished(cid, exit_code);
1160 Ok(())
1161 } else {
1162 error!("notifyPayloadFinished is called from an unknown CID {}", cid);
1163 Err(Status::new_service_specific_error_str(
1164 -1,
1165 Some(format!("cannot find a VM with CID {}", cid)),
1166 ))
1167 }
1168 }
1169
notifyError(&self, error_code: ErrorCode, message: &str) -> binder::Result<()>1170 fn notifyError(&self, error_code: ErrorCode, message: &str) -> binder::Result<()> {
1171 let cid = self.cid;
1172 if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
1173 info!("VM with CID {} encountered an error", cid);
1174 vm.update_payload_state(PayloadState::Finished).map_err(|e| {
1175 Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
1176 })?;
1177 vm.callbacks.notify_error(cid, error_code, message);
1178 Ok(())
1179 } else {
1180 error!("notifyError is called from an unknown CID {}", cid);
1181 Err(Status::new_service_specific_error_str(
1182 -1,
1183 Some(format!("cannot find a VM with CID {}", cid)),
1184 ))
1185 }
1186 }
1187 }
1188
1189 impl VirtualMachineService {
new_binder(state: Arc<Mutex<State>>, cid: Cid) -> Strong<dyn IVirtualMachineService>1190 fn new_binder(state: Arc<Mutex<State>>, cid: Cid) -> Strong<dyn IVirtualMachineService> {
1191 BnVirtualMachineService::new_binder(
1192 VirtualMachineService { state, cid },
1193 BinderFeatures::default(),
1194 )
1195 }
1196 }
1197
1198 #[cfg(test)]
1199 mod tests {
1200 use super::*;
1201
1202 #[test]
test_is_allowed_label_for_partition() -> Result<()>1203 fn test_is_allowed_label_for_partition() -> Result<()> {
1204 let expected_results = vec![
1205 ("u:object_r:system_file:s0", true),
1206 ("u:object_r:apk_data_file:s0", true),
1207 ("u:object_r:app_data_file:s0", false),
1208 ("u:object_r:app_data_file:s0:c512,c768", false),
1209 ("u:object_r:privapp_data_file:s0:c512,c768", false),
1210 ("invalid", false),
1211 ("user:role:apk_data_file:severity:categories", true),
1212 ("user:role:apk_data_file:severity:categories:extraneous", false),
1213 ];
1214
1215 for (label, expected_valid) in expected_results {
1216 let context = SeContext::new(label)?;
1217 let result = check_label_is_allowed(&context);
1218 if expected_valid {
1219 assert!(result.is_ok(), "Expected label {} to be allowed, got {:?}", label, result);
1220 } else if result.is_ok() {
1221 bail!("Expected label {} to be disallowed", label);
1222 }
1223 }
1224 Ok(())
1225 }
1226
1227 #[test]
test_create_or_update_idsig_file_empty_apk() -> Result<()>1228 fn test_create_or_update_idsig_file_empty_apk() -> Result<()> {
1229 let apk = tempfile::tempfile().unwrap();
1230 let idsig = tempfile::tempfile().unwrap();
1231
1232 let ret = create_or_update_idsig_file(
1233 &ParcelFileDescriptor::new(apk),
1234 &ParcelFileDescriptor::new(idsig),
1235 );
1236 assert!(ret.is_err(), "should fail");
1237 Ok(())
1238 }
1239
1240 #[test]
test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()>1241 fn test_create_or_update_idsig_dir_instead_of_file_for_apk() -> Result<()> {
1242 let tmp_dir = tempfile::TempDir::new().unwrap();
1243 let apk = File::open(tmp_dir.path()).unwrap();
1244 let idsig = tempfile::tempfile().unwrap();
1245
1246 let ret = create_or_update_idsig_file(
1247 &ParcelFileDescriptor::new(apk),
1248 &ParcelFileDescriptor::new(idsig),
1249 );
1250 assert!(ret.is_err(), "should fail");
1251 Ok(())
1252 }
1253
1254 /// Verifies that create_or_update_idsig_file won't oom if a fd that corresponds to a directory
1255 /// on ext4 filesystem is passed.
1256 /// On ext4 lseek on a directory fd will return (off_t)-1 (see:
1257 /// https://bugzilla.kernel.org/show_bug.cgi?id=200043), which will result in
1258 /// create_or_update_idsig_file ooming while attempting to allocate petabytes of memory.
1259 #[test]
test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()>1260 fn test_create_or_update_idsig_does_not_crash_dir_on_ext4() -> Result<()> {
1261 // APEXes are backed by the ext4.
1262 let apk = File::open("/apex/com.android.virt/").unwrap();
1263 let idsig = tempfile::tempfile().unwrap();
1264
1265 let ret = create_or_update_idsig_file(
1266 &ParcelFileDescriptor::new(apk),
1267 &ParcelFileDescriptor::new(idsig),
1268 );
1269 assert!(ret.is_err(), "should fail");
1270 Ok(())
1271 }
1272 }
1273