Virtualizationservice makes payload disk image
with given APK, idsig, and config file.
To exercise the new execution mode, 'vm run-app' sub command is added.
$ vm <apk_path> <idsig_path> <config_path>
For example,
$ vm /data/local/tmp/MyApp.apk /data/local/tmp/MyApp.apk.idsig \
assets/config.json
Bug: 190503456
Test: MicrodroidHostTestCases, VirtualizationTestCases
Change-Id: Iceec9b34e9785a1ae36452bfc2653c3c045f4dfa
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 700d0fc..a941742 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -27,12 +27,16 @@
"libcrc32fast",
"libdisk",
"liblog_rust",
+ "libmicrodroid_metadata",
+ "libmicrodroid_payload_config",
"libprotobuf",
"libprotos",
"libserde_json",
"libserde",
"libshared_child",
"libuuid",
+ "libvmconfig",
+ "libzip",
],
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
new file mode 100644
index 0000000..5b270a3
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualizationservice;
+
+/** Configuration for running an App in a VM */
+parcelable VirtualMachineAppConfig {
+ /** Main APK */
+ ParcelFileDescriptor apk;
+
+ /** idsig for an APK */
+ ParcelFileDescriptor idsig;
+
+ /** Path to a configuration in an APK. This is the actual configuration for a VM. */
+ @utf8InCpp String configPath;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
index 5d59f9d..00a7937 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineConfig.aidl
@@ -15,31 +15,14 @@
*/
package android.system.virtualizationservice;
-import android.system.virtualizationservice.DiskImage;
+import android.system.virtualizationservice.VirtualMachineAppConfig;
+import android.system.virtualizationservice.VirtualMachineRawConfig;
-/** Configuration for running a VM. */
-parcelable VirtualMachineConfig {
- /** The kernel image, if any. */
- @nullable ParcelFileDescriptor kernel;
+/** Configuration for running a VM */
+union VirtualMachineConfig {
+ /** Configuration for a VM to run an APP */
+ VirtualMachineAppConfig appConfig;
- /** The initial ramdisk for the kernel, if any. */
- @nullable ParcelFileDescriptor initrd;
-
- /**
- * Parameters to pass to the kernel. As far as the VMM and boot protocol are concerned this is
- * just a string, but typically it will contain multiple parameters separated by spaces.
- */
- @nullable @utf8InCpp String params;
-
- /**
- * The bootloader to use. If this is supplied then the kernel and initrd must not be supplied;
- * the bootloader is instead responsibly for loading the kernel from one of the disks.
- */
- @nullable ParcelFileDescriptor bootloader;
-
- /** Disk images to be made available to the VM. */
- DiskImage[] disks;
-
- /** Whether the VM should be a protected VM. */
- boolean protected_vm;
+ /** Configuration for a VM with low-level configuration */
+ VirtualMachineRawConfig rawConfig;
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
new file mode 100644
index 0000000..7848ed5
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.system.virtualizationservice;
+
+import android.system.virtualizationservice.DiskImage;
+
+/** Raw Configuration for running a VM. */
+parcelable VirtualMachineRawConfig {
+ /** The kernel image, if any. */
+ @nullable ParcelFileDescriptor kernel;
+
+ /** The initial ramdisk for the kernel, if any. */
+ @nullable ParcelFileDescriptor initrd;
+
+ /**
+ * Parameters to pass to the kernel. As far as the VMM and boot protocol are concerned this is
+ * just a string, but typically it will contain multiple parameters separated by spaces.
+ */
+ @nullable @utf8InCpp String params;
+
+ /**
+ * The bootloader to use. If this is supplied then the kernel and initrd must not be supplied;
+ * the bootloader is instead responsibly for loading the kernel from one of the disks.
+ */
+ @nullable ParcelFileDescriptor bootloader;
+
+ /** Disk images to be made available to the VM. */
+ DiskImage[] disks;
+
+ /** Whether the VM should be a protected VM. */
+ boolean protected_vm;
+}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index ce9a080..3f1660e 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -16,26 +16,35 @@
use crate::composite::make_composite_image;
use crate::crosvm::{CrosvmConfig, DiskFile, VmInstance};
+use crate::payload;
use crate::{Cid, FIRST_GUEST_CID};
+
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DiskImage::DiskImage;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachine::{
BnVirtualMachine, IVirtualMachine,
};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualMachineCallback::IVirtualMachineCallback;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+ VirtualMachineAppConfig::VirtualMachineAppConfig,
+ VirtualMachineConfig::VirtualMachineConfig,
+ VirtualMachineRawConfig::VirtualMachineRawConfig,
+};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
use android_system_virtualizationservice::binder::{
self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
};
+use anyhow::Error;
use disk::QcowFile;
use log::{debug, error, warn};
+use microdroid_payload_config::VmPayloadConfig;
use std::convert::TryInto;
use std::ffi::CString;
use std::fs::{File, create_dir};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
+use vmconfig::VmConfig;
pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
@@ -92,6 +101,22 @@
)
})?;
+ let mut opt_raw_config = None;
+ let config = match config {
+ VirtualMachineConfig::AppConfig(config) => {
+ let raw_config = load_app_config(config, &temporary_directory).map_err(|e| {
+ error!("Failed to load app config from {}: {}", &config.configPath, e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to load app config from {}: {}", &config.configPath, e),
+ )
+ })?;
+ opt_raw_config.replace(raw_config);
+ opt_raw_config.as_ref().unwrap()
+ }
+ VirtualMachineConfig::RawConfig(config) => config,
+ };
+
// Assemble disk images if needed.
let disks = config
.disks
@@ -256,6 +281,34 @@
Ok(DiskFile { image, writable: disk.writable })
}
+fn load_app_config(
+ config: &VirtualMachineAppConfig,
+ temporary_directory: &Path,
+) -> Result<VirtualMachineRawConfig, Error> {
+ let apk_file = config.apk.as_ref().unwrap().as_ref();
+ let idsig_file = config.idsig.as_ref().unwrap().as_ref();
+ let config_path = &config.configPath;
+
+ let mut apk_zip = zip::ZipArchive::new(apk_file)?;
+ let config_file = apk_zip.by_name(config_path)?;
+ let vm_payload_config: VmPayloadConfig = serde_json::from_reader(config_file)?;
+
+ let os_name = &vm_payload_config.os.name;
+ let vm_config_path = PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name));
+ let vm_config_file = File::open(vm_config_path)?;
+ let mut vm_config = VmConfig::load(&vm_config_file)?;
+
+ vm_config.disks.push(payload::make_disk_image(
+ format!("/proc/self/fd/{}", apk_file.as_raw_fd()).into(),
+ format!("/proc/self/fd/{}", idsig_file.as_raw_fd()).into(),
+ config_path,
+ &vm_payload_config.apexes,
+ temporary_directory,
+ )?);
+
+ vm_config.to_parcelable()
+}
+
/// Generates a unique filename to use for a composite disk image.
fn make_composite_image_filenames(
temporary_directory: &Path,
diff --git a/virtualizationservice/src/composite.rs b/virtualizationservice/src/composite.rs
index 7b5a258..1af0eed 100644
--- a/virtualizationservice/src/composite.rs
+++ b/virtualizationservice/src/composite.rs
@@ -72,6 +72,11 @@
((val + (align - 1)) / align) * align
}
+/// Round `val` to partition size(4K)
+pub fn align_to_partition_size(val: u64) -> u64 {
+ align_to_power_of_2(val, PARTITION_SIZE_SHIFT)
+}
+
impl PartitionInfo {
fn aligned_size(&self) -> u64 {
align_to_power_of_2(self.files.iter().map(|file| file.size).sum(), PARTITION_SIZE_SHIFT)
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 43b5fe4..658203b 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -18,6 +18,7 @@
mod composite;
mod crosvm;
mod gpt;
+mod payload;
use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
new file mode 100644
index 0000000..19a6d9f
--- /dev/null
+++ b/virtualizationservice/src/payload.rs
@@ -0,0 +1,140 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Payload disk image
+
+use crate::composite::align_to_partition_size;
+
+use anyhow::{Error, Result};
+use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
+use microdroid_payload_config::ApexConfig;
+use std::fs;
+use std::fs::OpenOptions;
+use std::io::{Seek, SeekFrom, Write};
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use vmconfig::{DiskImage, Partition};
+
+// TODO(b/191601801): look up /apex/apex-info-list.xml
+fn get_path(package_name: &str) -> Result<PathBuf> {
+ let output = Command::new("pm").arg("path").arg(package_name).output()?;
+ let output = String::from_utf8(output.stdout)?;
+ Ok(PathBuf::from(output.strip_prefix("package:").unwrap().trim()))
+}
+
+/// When passing a host APEX file as a block device in a payload disk image,
+/// the size of the original file needs to be stored in the last 4 bytes so that
+/// other programs (e.g. apexd) can read it as a zip.
+fn make_size_filler(size: u64, filler_path: &Path) -> Result<bool> {
+ let partition_size = align_to_partition_size(size + 4);
+ let mut file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
+ file.set_len(partition_size - size)?;
+ file.seek(SeekFrom::End(-4))?;
+ file.write_all(&(size as i32).to_be_bytes())?;
+ Ok(true)
+}
+
+/// When passing a host APK file as a block device in a payload disk image and it is
+/// mounted via dm-verity, we need to make the device zero-padded up to 4K boundary.
+/// Otherwise, intergrity checks via hashtree will fail.
+fn make_zero_filler(size: u64, filler_path: &Path) -> Result<bool> {
+ let partition_size = align_to_partition_size(size);
+ if partition_size <= size {
+ return Ok(false);
+ }
+ let file = OpenOptions::new().create_new(true).write(true).open(filler_path)?;
+ file.set_len(partition_size - size)?;
+ Ok(true)
+}
+
+/// When passing a host idsig file as a block device, we don't need any filler because it is read
+/// in length-prefixed way.
+fn make_no_filler(_size: u64, _filler_path: &Path) -> Result<bool> {
+ Ok(false)
+}
+
+/// Creates a DiskImage with partitions:
+/// metadata: metadata
+/// microdroid-apex-0: [apex 0, size filler]
+/// microdroid-apex-1: [apex 1, size filler]
+/// ..
+/// microdroid-apk: [apk, zero filler]
+/// microdroid-apk-idsig: idsig
+pub fn make_disk_image(
+ apk_file: PathBuf,
+ idsig_file: PathBuf,
+ config_path: &str,
+ apexes: &[ApexConfig],
+ temporary_directory: &Path,
+) -> Result<DiskImage> {
+ let metadata_path = temporary_directory.join("metadata");
+ let metadata = Metadata {
+ version: 1u32,
+ apexes: apexes
+ .iter()
+ .map(|apex| ApexPayload { name: String::from(&apex.name), ..Default::default() })
+ .collect(),
+ apk: Some(ApkPayload {
+ name: String::from("apk"),
+ payload_partition_name: String::from("microdroid-apk"),
+ idsig_partition_name: String::from("microdroid-apk-idsig"),
+ ..Default::default()
+ })
+ .into(),
+ payload_config_path: format!("/mnt/apk/{}", config_path),
+ ..Default::default()
+ };
+ let mut metadata_file =
+ OpenOptions::new().create_new(true).read(true).write(true).open(&metadata_path)?;
+ microdroid_metadata::write_metadata(&metadata, &mut metadata_file)?;
+
+ // put metadata at the first partition
+ let mut partitions = vec![Partition {
+ label: String::from("metadata"),
+ path: Some(metadata_path),
+ paths: vec![],
+ writable: false,
+ }];
+
+ let mut filler_count = 0;
+ let mut make_partition = |label: String,
+ path: PathBuf,
+ make_filler: &dyn Fn(u64, &Path) -> Result<bool, Error>|
+ -> Result<Partition> {
+ let filler_path = temporary_directory.join(format!("filler-{}", filler_count));
+ let size = fs::metadata(&path)?.len();
+
+ if make_filler(size, &filler_path)? {
+ filler_count += 1;
+ Ok(Partition { label, path: None, paths: vec![path, filler_path], writable: false })
+ } else {
+ Ok(Partition { label, path: Some(path), paths: vec![], writable: false })
+ }
+ };
+ for (i, apex) in apexes.iter().enumerate() {
+ partitions.push(make_partition(
+ format!("microdroid-apex-{}", i),
+ get_path(&apex.name)?,
+ &make_size_filler,
+ )?);
+ }
+ partitions.push(make_partition(String::from("microdroid-apk"), apk_file, &make_zero_filler)?);
+ partitions.push(make_partition(
+ String::from("microdroid-apk-idsig"),
+ idsig_file,
+ &make_no_filler,
+ )?);
+
+ Ok(DiskImage { image: None, partitions, writable: false })
+}