Merge "Remove wait_for_keymaster and all references"
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..b655551
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 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 {
+ default_applicable_licenses: ["packages_modules_Virtualization_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "packages_modules_Virtualization_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "legacy_unencumbered",
+ ],
+ // large-scale-change unable to identify any license_text files
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 15099da..13c68d7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -2,6 +2,9 @@
"postsubmit": [
{
"name": "MicrodroidHostTestCases"
+ },
+ {
+ "name": "VirtualizationTestCases"
}
],
"imports": [
diff --git a/apex/Android.bp b/apex/Android.bp
index 3db4c1a..146e4eb 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,12 +13,13 @@
key: "com.android.virt.key",
certificate: ":com.android.virt.certificate",
- // crosvm is enabled for only 64-bit targets on device
+ // crosvm and virtualizationservice are only enabled for 64-bit targets on device
arch: {
arm64: {
binaries: [
"authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
"crosvm",
+ "virtualizationservice",
],
filesystems: [
"microdroid_super",
@@ -32,6 +33,7 @@
binaries: [
"authfs", // TODO(victorhsieh): move to microdroid once we can run the test in VM.
"crosvm",
+ "virtualizationservice",
],
filesystems: [
"microdroid_super",
@@ -44,23 +46,16 @@
},
binaries: [
"fd_server",
- "virtualizationservice",
"vm",
// tools to create composite images
"mk_cdisk",
"mk_payload",
],
- apps: [
- // TODO(jiyong): remove this when microdroid_payload.json is created by
- // VirtualizationService.
- "MicrodroidTestApp",
- ],
prebuilts: [
"com.android.virt.init.rc",
"microdroid_cdisk.json",
"microdroid_cdisk_env.json",
- "microdroid_payload.json",
"microdroid_uboot_env",
"microdroid_bootloader",
],
diff --git a/apkdmverity/src/loopdevice.rs b/apkdmverity/src/loopdevice.rs
index 69920d5..376abd4 100644
--- a/apkdmverity/src/loopdevice.rs
+++ b/apkdmverity/src/loopdevice.rs
@@ -37,7 +37,8 @@
// These are old-style ioctls, thus *_bad.
nix::ioctl_none_bad!(_loop_ctl_get_free, LOOP_CTL_GET_FREE);
-nix::ioctl_write_ptr_bad!(_loop_configure, LOOP_CONFIGURE, loop_config);
+nix::ioctl_write_int_bad!(_loop_set_fd, LOOP_SET_FD);
+nix::ioctl_write_ptr_bad!(_loop_set_status64, LOOP_SET_STATUS64, loop_info64);
#[cfg(test)]
nix::ioctl_none_bad!(_loop_clr_fd, LOOP_CLR_FD);
@@ -48,9 +49,14 @@
Ok(unsafe { _loop_ctl_get_free(ctrl_file.as_raw_fd()) }?)
}
-fn loop_configure(device_file: &File, config: &loop_config) -> Result<i32> {
+fn loop_set_fd(device_file: &File, fd: i32) -> Result<i32> {
// SAFETY: this ioctl changes the state in kernel, but not the state in this process.
- Ok(unsafe { _loop_configure(device_file.as_raw_fd(), config) }?)
+ Ok(unsafe { _loop_set_fd(device_file.as_raw_fd(), fd) }?)
+}
+
+fn loop_set_status64(device_file: &File, info: &loop_info64) -> Result<i32> {
+ // SAFETY: this ioctl changes the state in kernel, but not the state in this process.
+ Ok(unsafe { _loop_set_status64(device_file.as_raw_fd(), info) }?)
}
#[cfg(test)]
@@ -103,26 +109,24 @@
.context("Failed to open loop control")?;
let num = loop_ctl_get_free(&ctrl_file).context("Failed to get free loop device")?;
- // Construct the loop_config struct
+ // Construct the loop_info64 struct
let backing_file = OpenOptions::new()
.read(true)
.open(&path)
.context(format!("failed to open {:?}", path.as_ref()))?;
// safe because the size of the array is the same as the size of the struct
- let mut config: loop_config =
- *DataInit::from_mut_slice(&mut [0; size_of::<loop_config>()]).unwrap();
- config.fd = backing_file.as_raw_fd() as u32;
- config.block_size = 4096;
- config.info.lo_offset = offset;
- config.info.lo_sizelimit = size_limit;
- config.info.lo_flags |= Flag::LO_FLAGS_DIRECT_IO | Flag::LO_FLAGS_READ_ONLY;
+ let mut info: loop_info64 =
+ *DataInit::from_mut_slice(&mut [0; size_of::<loop_info64>()]).unwrap();
+ info.lo_offset = offset;
+ info.lo_sizelimit = size_limit;
+ info.lo_flags |= Flag::LO_FLAGS_DIRECT_IO | Flag::LO_FLAGS_READ_ONLY;
// Special case: don't use direct IO when the backing file is already a loop device, which
// happens only during test. DirectIO-on-loop-over-loop makes the outer loop device
// unaccessible.
#[cfg(test)]
if path.as_ref().to_str().unwrap().starts_with(LOOP_DEV_PREFIX) {
- config.info.lo_flags.remove(Flag::LO_FLAGS_DIRECT_IO);
+ info.lo_flags.remove(Flag::LO_FLAGS_DIRECT_IO);
}
// Configure the loop device to attach the backing file
@@ -133,8 +137,8 @@
.write(true)
.open(&device_path)
.context(format!("failed to open {:?}", &device_path))?;
- loop_configure(&device_file, &config)
- .context(format!("Failed to configure {:?}", &device_path))?;
+ loop_set_fd(&device_file, backing_file.as_raw_fd() as i32)?;
+ loop_set_status64(&device_file, &info)?;
Ok(PathBuf::from(device_path))
}
diff --git a/apkdmverity/src/loopdevice/sys.rs b/apkdmverity/src/loopdevice/sys.rs
index 5de0c92..d32987a 100644
--- a/apkdmverity/src/loopdevice/sys.rs
+++ b/apkdmverity/src/loopdevice/sys.rs
@@ -24,7 +24,8 @@
pub const LOOP_CONTROL: &str = "/dev/loop-control";
pub const LOOP_CTL_GET_FREE: libc::c_ulong = 0x4C82;
-pub const LOOP_CONFIGURE: libc::c_ulong = 0x4C0A;
+pub const LOOP_SET_FD: libc::c_ulong = 0x4C00;
+pub const LOOP_SET_STATUS64: libc::c_ulong = 0x4C04;
#[cfg(test)]
pub const LOOP_CLR_FD: libc::c_ulong = 0x4C01;
@@ -58,6 +59,9 @@
pub lo_init: [u64; 2],
}
+// SAFETY: C struct is safe to be initialized from raw data
+unsafe impl DataInit for loop_info64 {}
+
bitflags! {
pub struct Flag: u32 {
const LO_FLAGS_READ_ONLY = 1 << 0;
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 5094c50..54d5def 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -37,7 +37,7 @@
use std::path::{Path, PathBuf};
fn main() -> Result<()> {
- let matches = App::new("apkverity")
+ let matches = App::new("apkdmverity")
.about("Creates a dm-verity block device out of APK signed with APK signature scheme V4.")
.arg(
Arg::with_name("apk")
diff --git a/compos/Android.bp b/compos/Android.bp
index fc0517f..1611b68 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -55,3 +55,10 @@
"com.android.compos",
],
}
+
+// TODO(b/190503456) Remove this when vm/virtualizationservice generates payload.img from vm_config
+prebuilt_etc {
+ name: "compos_payload_config",
+ src: "payload_config.json",
+ filename: "payload_config.json",
+}
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 7ced384..3a8f601 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -40,4 +40,12 @@
"compsvc_worker",
"pvm_exec",
],
+
+ apps: [
+ "CompOSPayloadApp",
+ ],
+
+ prebuilts: [
+ "compos_payload_config",
+ ],
}
diff --git a/compos/apk/Android.bp b/compos/apk/Android.bp
new file mode 100644
index 0000000..c6192b9
--- /dev/null
+++ b/compos/apk/Android.bp
@@ -0,0 +1,9 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "CompOSPayloadApp",
+ sdk_version: "current",
+ apex_available: ["com.android.compos"],
+}
diff --git a/compos/apk/AndroidManifest.xml b/compos/apk/AndroidManifest.xml
new file mode 100644
index 0000000..1e9352b
--- /dev/null
+++ b/compos/apk/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.compos.payload">
+ <application android:label="CompOS" />
+</manifest>
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
new file mode 100644
index 0000000..a8dca71
--- /dev/null
+++ b/compos/apk/assets/vm_config.json
@@ -0,0 +1,34 @@
+{
+ "version": 1,
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "executable",
+ "command": "/apex/com.android.compos/bin/compsvc",
+ "args": [
+ "--rpc-binder",
+ "/apex/com.android.art/bin/dex2oat64"
+ ]
+ },
+ "apexes": [
+ {
+ "name": "com.android.adbd"
+ },
+ {
+ "name": "com.android.art"
+ },
+ {
+ "name": "com.android.compos"
+ },
+ {
+ "name": "com.android.i18n"
+ },
+ {
+ "name": "com.android.os.statsd"
+ },
+ {
+ "name": "com.android.sdkext"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/compos/payload_config.json b/compos/payload_config.json
new file mode 100644
index 0000000..588ccca
--- /dev/null
+++ b/compos/payload_config.json
@@ -0,0 +1,15 @@
+{
+ "apk": {
+ "path": "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk",
+ "name": "com.android.compos.payload"
+ },
+ "system_apexes": [
+ "com.android.adbd",
+ "com.android.art",
+ "com.android.compos",
+ "com.android.i18n",
+ "com.android.os.statsd",
+ "com.android.sdkext"
+ ],
+ "payload_config_path": "/mnt/apk/assets/vm_config.json"
+}
\ No newline at end of file
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index ec766b5..d350b56 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -49,6 +49,7 @@
"microdroid_init_rc",
"microdroid_launcher",
"microdroid_manager",
+
"ueventd.rc",
"libbinder",
"libbinder_ndk",
@@ -98,6 +99,7 @@
},
lib64: {
deps: [
+ "apkdmverity",
"zipfuse",
],
},
@@ -397,11 +399,6 @@
}
prebuilt_etc {
- name: "microdroid_payload.json",
- src: "microdroid_payload.json",
-}
-
-prebuilt_etc {
name: "microdroid_vendor_manifest",
src: "microdroid_vendor_manifest.xml",
filename: "manifest.xml",
diff --git a/microdroid/README.md b/microdroid/README.md
index 6b9f4b1..aae6e66 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -42,51 +42,33 @@
## Running
-Create a config file, `microdroid.json`:
+Create two config files, `microdroid.json` and `payload.json`:
```json
+microdroid.json:
+
{
"bootloader": "/apex/com.android.virt/etc/microdroid_bootloader",
"disks": [
{
"partitions": [
{
- "label": "misc",
- "path": "/data/local/tmp/microdroid/misc.img"
- },
- {
"label": "boot_a",
"path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
},
{
- "label": "boot_b",
- "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
- },
- {
"label": "vendor_boot_a",
"path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
},
{
- "label": "vendor_boot_b",
- "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
- },
- {
"label": "vbmeta_a",
"path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
},
{
- "label": "vbmeta_b",
- "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
- },
- {
"label": "vbmeta_system_a",
"path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
},
{
- "label": "vbmeta_system_b",
- "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
- },
- {
"label": "super",
"path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
}
@@ -108,9 +90,20 @@
}
]
}
+
+payload.json:
+
+{
+ "system_apexes" : [
+ "com.android.adbd",
+ "com.android.i18n",
+ "com.android.os.statsd",
+ "com.android.sdkext"
+ ]
+}
```
-Copy the artifacts to the temp directory, create the composite image using
+Copy the these files to the temp directory, create the composite image using
`mk_cdisk` and copy the VM config file. For now, some other files have to be
manually created. In the future, you won't need these, and this shall be done
via [`virtualizationservice`](../virtualizationservice/).
@@ -118,8 +111,8 @@
```sh
$ adb root
$ adb shell 'mkdir /data/local/tmp/microdroid'
-$ adb shell 'dd if=/dev/zero of=/data/local/tmp/microdroid/misc.img bs=4k count=256'
-$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_payload /apex/com.android.virt/etc/microdroid_payload.json payload.img'
+$ adb push payload.json /data/local/tmp/microdroid/payload.json
+$ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_payload payload.json payload.img'
$ adb shell 'chmod go+r /data/local/tmp/microdroid/payload*'
$ adb push microdroid.json /data/local/tmp/microdroid/microdroid.json
```
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 319794c..861b032 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -22,6 +22,8 @@
start ueventd
+ # TODO(b/190343842) verify apexes/apk before mounting them.
+
# Exec apexd in the VM mode to avoid unnecessary overhead of normal mode.
# (e.g. session management)
exec - root system -- /system/bin/apexd --vm
@@ -29,6 +31,7 @@
perform_apex_config
exec_start derive_sdk
+ exec - root system -- /system/bin/apkdmverity /dev/block/by-name/microdroid-apk /dev/block/by-name/microdroid-apk-idsig microdroid-apk
mkdir /mnt/apk 0755 system system
start zipfuse
diff --git a/microdroid/microdroid_cdisk.json b/microdroid/microdroid_cdisk.json
index 5721775..d0a5025 100644
--- a/microdroid/microdroid_cdisk.json
+++ b/microdroid/microdroid_cdisk.json
@@ -1,44 +1,24 @@
{
"partitions": [
{
- "label": "misc",
- "path": "misc.img"
- },
- {
"label": "boot_a",
- "path": "microdroid_boot-5.10.img"
- },
- {
- "label": "boot_b",
- "path": "microdroid_boot-5.10.img"
+ "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
},
{
"label": "vendor_boot_a",
- "path": "microdroid_vendor_boot-5.10.img"
- },
- {
- "label": "vendor_boot_b",
- "path": "microdroid_vendor_boot-5.10.img"
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
},
{
"label": "vbmeta_a",
- "path": "microdroid_vbmeta.img"
- },
- {
- "label": "vbmeta_b",
- "path": "microdroid_vbmeta.img"
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta.img"
},
{
"label": "vbmeta_system_a",
- "path": "microdroid_vbmeta_system.img"
- },
- {
- "label": "vbmeta_system_b",
- "path": "microdroid_vbmeta_system.img"
+ "path": "/apex/com.android.virt/etc/fs/microdroid_vbmeta_system.img"
},
{
"label": "super",
- "path": "microdroid_super.img"
+ "path": "/apex/com.android.virt/etc/fs/microdroid_super.img"
}
]
}
diff --git a/microdroid/microdroid_cdisk_env.json b/microdroid/microdroid_cdisk_env.json
index b0fbe44..8c84ed5 100644
--- a/microdroid/microdroid_cdisk_env.json
+++ b/microdroid/microdroid_cdisk_env.json
@@ -2,7 +2,7 @@
"partitions": [
{
"label": "uboot_env",
- "path": "uboot_env.img"
+ "path": "/apex/com.android.virt/etc/uboot_env.img"
}
]
}
diff --git a/microdroid/microdroid_payload.json b/microdroid/microdroid_payload.json
deleted file mode 100644
index 7af0452..0000000
--- a/microdroid/microdroid_payload.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "system_apexes": [
- "com.android.adbd",
- "com.android.i18n",
- "com.android.os.statsd",
- "com.android.sdkext"
- ],
- "apk": {
- "name": "com.android.microdroid.test",
- "path": "/apex/com.android.virt/app/MicrodroidTestApp/MicrodroidTestApp.apk"
- },
- "payload_config_path": "/mnt/apk/assets/vm_config.json"
-}
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 1da71de..c31dcff 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -87,8 +87,9 @@
struct ApkConfig {
std::string name;
- // TODO(jooyung): find path/idsig with name
std::string path;
+ // TODO(jooyung) make this required?
+ std::optional<std::string> idsig_path;
};
struct Config {
@@ -144,6 +145,7 @@
Result<void> ParseJson(const Json::Value& value, ApkConfig& apk_config) {
DO(ParseJson(value["name"], apk_config.name));
DO(ParseJson(value["path"], apk_config.path));
+ DO(ParseJson(value["idsig_path"], apk_config.idsig_path));
return {};
}
@@ -226,7 +228,9 @@
auto* apk = metadata.mutable_apk();
apk->set_name(config.apk->name);
apk->set_payload_partition_name("microdroid-apk");
- // TODO(jooyung): set idsig partition as well
+ if (config.apk->idsig_path.has_value()) {
+ apk->set_idsig_partition_name("microdroid-apk-idsig");
+ }
}
if (config.payload_config_path.has_value()) {
@@ -237,7 +241,9 @@
return WriteMetadata(metadata, out);
}
-Result<void> GenerateFiller(const std::string& file_path, const std::string& filler_path) {
+// fill (zeros + original file's size) with aligning BLOCK_SIZE(4096) boundary
+// return true when the filler is generated.
+Result<bool> SizeFiller(const std::string& file_path, const std::string& filler_path) {
auto file_size = GetFileSize(file_path);
if (!file_size.ok()) {
return file_size.error();
@@ -258,7 +264,36 @@
if (write(fd.get(), &size, sizeof(size)) <= 0) {
return ErrnoError() << "write(" << filler_path << ") failed.";
}
- return {};
+ return true;
+}
+
+// fill zeros to align |file_path|'s size to BLOCK_SIZE(4096) boundary.
+// return true when the filler is generated.
+Result<bool> ZeroFiller(const std::string& file_path, const std::string& filler_path) {
+ auto file_size = GetFileSize(file_path);
+ if (!file_size.ok()) {
+ return file_size.error();
+ }
+ auto disk_size = AlignToPartitionSize(*file_size);
+ if (disk_size <= *file_size) {
+ return false;
+ }
+ unique_fd fd(TEMP_FAILURE_RETRY(open(filler_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0600)));
+ if (fd.get() == -1) {
+ return ErrnoError() << "open(" << filler_path << ") failed.";
+ }
+ if (ftruncate(fd.get(), disk_size - *file_size) == -1) {
+ return ErrnoError() << "ftruncate(" << filler_path << ") failed.";
+ }
+ return true;
+}
+
+// Do not generate any fillers
+// Note that CreateCompositeDisk() handles gaps between partitions.
+Result<bool> NoFiller(const std::string& file_path, const std::string& filler_path) {
+ (void)file_path;
+ (void)filler_path;
+ return false;
}
Result<void> MakePayload(const Config& config, const std::string& metadata_file,
@@ -274,14 +309,18 @@
});
int filler_count = 0;
- auto add_partition = [&](auto partition_name, auto file_path) -> Result<void> {
+ auto add_partition = [&](auto partition_name, auto file_path, auto filler) -> Result<void> {
+ std::vector<std::string> image_files{file_path};
+
std::string filler_path = output_file + "." + std::to_string(filler_count++);
- if (auto ret = GenerateFiller(file_path, filler_path); !ret.ok()) {
+ if (auto ret = filler(file_path, filler_path); !ret.ok()) {
return ret.error();
+ } else if (*ret) {
+ image_files.push_back(filler_path);
}
partitions.push_back(MultipleImagePartition{
.label = partition_name,
- .image_file_paths = {file_path, filler_path},
+ .image_file_paths = image_files,
.type = kLinuxFilesystem,
.read_only = true,
});
@@ -292,18 +331,24 @@
for (size_t i = 0; i < config.apexes.size(); i++) {
const auto& apex_config = config.apexes[i];
std::string apex_path = ToAbsolute(apex_config.path, config.dirname);
- if (auto ret = add_partition("microdroid-apex-" + std::to_string(i), apex_path);
+ if (auto ret = add_partition("microdroid-apex-" + std::to_string(i), apex_path, SizeFiller);
!ret.ok()) {
return ret.error();
}
}
- // put apk with "size" filler if necessary.
+ // put apk with "zero" filler.
// TODO(jooyung): partition name("microdroid-apk") is TBD
if (config.apk.has_value()) {
std::string apk_path = ToAbsolute(config.apk->path, config.dirname);
- if (auto ret = add_partition("microdroid-apk", apk_path); !ret.ok()) {
+ if (auto ret = add_partition("microdroid-apk", apk_path, ZeroFiller); !ret.ok()) {
return ret.error();
}
+ if (config.apk->idsig_path.has_value()) {
+ std::string idsig_path = ToAbsolute(config.apk->idsig_path.value(), config.dirname);
+ if (auto ret = add_partition("microdroid-apk-idsig", idsig_path, NoFiller); !ret.ok()) {
+ return ret.error();
+ }
+ }
}
const std::string gpt_header = AppendFileName(output_file, "-header");
diff --git a/microdroid/sepolicy/Android.bp b/microdroid/sepolicy/Android.bp
index 9bb6408..6488153 100644
--- a/microdroid/sepolicy/Android.bp
+++ b/microdroid/sepolicy/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Virtualization_license"
+ // to get the below license kinds:
+ // legacy_unencumbered
+ default_applicable_licenses: ["packages_modules_Virtualization_license"],
+}
+
system_policy_files = [
"system/private/security_classes",
"system/private/initial_sids",
diff --git a/microdroid/sepolicy/system/private/apkdmverity.te b/microdroid/sepolicy/system/private/apkdmverity.te
new file mode 100644
index 0000000..c6160be
--- /dev/null
+++ b/microdroid/sepolicy/system/private/apkdmverity.te
@@ -0,0 +1,29 @@
+# apkdmverity is a program that protects a signed APK file using dm-verity.
+
+type apkdmverity, domain, coredomain;
+type apkdmverity_exec, exec_type, file_type, system_file_type;
+
+# allow domain transition from init
+init_daemon_domain(apkdmverity)
+
+# apkdmverity accesses /dev/block/by-name/metadata which points to
+# a /dev/vd* block device file.
+allow apkdmverity block_device:dir r_dir_perms;
+allow apkdmverity block_device:lnk_file r_file_perms;
+allow apkdmverity vd_device:blk_file r_file_perms;
+
+# allow apkdmverity to create dm-verity devices
+allow apkdmverity dm_device:{chr_file blk_file} rw_file_perms;
+# sys_admin is required to access the device-mapper and mount
+allow apkdmverity self:global_capability_class_set sys_admin;
+
+# allow apkdmverity to create loop devices with /dev/loop-control
+allow apkdmverity loop_control_device:chr_file rw_file_perms;
+
+# allow apkdmverity to access loop devices
+allow apkdmverity loop_device:blk_file rw_file_perms;
+allowxperm apkdmverity loop_device:blk_file ioctl {
+ LOOP_SET_STATUS64
+ LOOP_SET_FD
+ LOOP_SET_DIRECT_IO
+};
diff --git a/microdroid/sepolicy/system/private/domain.te b/microdroid/sepolicy/system/private/domain.te
index c451fcf..4a59f73 100644
--- a/microdroid/sepolicy/system/private/domain.te
+++ b/microdroid/sepolicy/system/private/domain.te
@@ -501,7 +501,8 @@
# Only init and otapreopt_chroot should be mounting filesystems on locations
# labeled system or vendor (/product and /vendor respectively).
-neverallow { domain -init -otapreopt_chroot } { system_file_type vendor_file_type }:dir_file_class_set mounton;
+# In microdroid, zipfuse is allowed mounton /mnt/apk.
+neverallow { domain -init -otapreopt_chroot -zipfuse } { system_file_type vendor_file_type }:dir_file_class_set mounton;
# Only allow init and vendor_init to read/write mm_events properties
# NOTE: dumpstate is allowed to read any system property
diff --git a/microdroid/sepolicy/system/private/file_contexts b/microdroid/sepolicy/system/private/file_contexts
index 9ba46d7..c4b4b2c 100644
--- a/microdroid/sepolicy/system/private/file_contexts
+++ b/microdroid/sepolicy/system/private/file_contexts
@@ -376,6 +376,7 @@
/system/bin/zipfuse u:object_r:zipfuse_exec:s0
/system/bin/microdroid_launcher u:object_r:microdroid_launcher_exec:s0
/system/bin/microdroid_manager u:object_r:microdroid_manager_exec:s0
+/system/bin/apkdmverity u:object_r:apkdmverity_exec:s0
#############################
# Vendor files
diff --git a/microdroid/sepolicy/system/private/kernel.te b/microdroid/sepolicy/system/private/kernel.te
index 5341163..2d49445 100644
--- a/microdroid/sepolicy/system/private/kernel.te
+++ b/microdroid/sepolicy/system/private/kernel.te
@@ -28,6 +28,12 @@
allow kernel null_device:chr_file relabelto;
allow kernel random_device:chr_file relabelto;
allow kernel snapuserd_exec:file relabelto;
+allow kernel vd_device:blk_file read;
allow kernel kmsg_device:chr_file write;
allow kernel gsid:fd use;
+
+# apkdmverity attaches a loop device to idsig file
+# and the loop device is used by zipfuse later.
+# This requires kernel to use the fd opened by apkdmverity.
+allow kernel apkdmverity:fd use;
diff --git a/microdroid/sepolicy/system/private/microdroid_launcher.te b/microdroid/sepolicy/system/private/microdroid_launcher.te
index e68b687..5a313b6 100644
--- a/microdroid/sepolicy/system/private/microdroid_launcher.te
+++ b/microdroid/sepolicy/system/private/microdroid_launcher.te
@@ -21,3 +21,6 @@
# Allow to use terminal
allow microdroid_launcher devpts:chr_file rw_file_perms;
+
+# Allow to set debug prop
+set_prop(microdroid_launcher, debug_prop)
diff --git a/microdroid/sepolicy/system/private/microdroid_manager.te b/microdroid/sepolicy/system/private/microdroid_manager.te
index f2feca2..deb969c 100644
--- a/microdroid/sepolicy/system/private/microdroid_manager.te
+++ b/microdroid/sepolicy/system/private/microdroid_manager.te
@@ -21,6 +21,12 @@
# Until then, allow microdroid_manager to execute the shell or other system executables.
allow microdroid_manager {shell_exec toolbox_exec}:file rx_file_perms;
+# Let microdroid_manager kernel-log.
+# TODO(b/189805435) when ready this should be kmsg_device rather than kmsg_debug_device
+userdebug_or_eng(`
+ allow microdroid_manager kmsg_debug_device:chr_file write;
+')
+
# Let microdroid_manager read a config file from /mnt/apk (fusefs)
# TODO(b/188400186) remove the below two rules
userdebug_or_eng(`
diff --git a/microdroid/sepolicy/system/private/zipfuse.te b/microdroid/sepolicy/system/private/zipfuse.te
index 65da9d3..fb7527b 100644
--- a/microdroid/sepolicy/system/private/zipfuse.te
+++ b/microdroid/sepolicy/system/private/zipfuse.te
@@ -17,7 +17,9 @@
# /dev/block/by-name/*
allow zipfuse block_device:dir r_dir_perms;
allow zipfuse block_device:lnk_file r_file_perms;
-allow zipfuse vd_device:blk_file r_file_perms;
+
+# /dev/block/by-name/microdroid-apk is mapped to /dev/block/dm-*
+allow zipfuse dm_device:blk_file r_file_perms;
# allow mounting on /mnt/apk
allow zipfuse tmpfs:dir mounton;
diff --git a/microdroid/uboot-env-x86_64.txt b/microdroid/uboot-env-x86_64.txt
index ffc0462..1abafa6 100644
--- a/microdroid/uboot-env-x86_64.txt
+++ b/microdroid/uboot-env-x86_64.txt
@@ -1,7 +1,9 @@
# Static u-boot environment variables for microdroid. See b/180481192
# Boot the device following the Android boot procedure
-bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
+# `0` is the disk number of os_composite.img
+# `a` and `_a` are the slot index for A/B
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0 a
bootdelay=0
diff --git a/microdroid/uboot-env.txt b/microdroid/uboot-env.txt
index 0bdc591..585702e 100644
--- a/microdroid/uboot-env.txt
+++ b/microdroid/uboot-env.txt
@@ -1,7 +1,9 @@
# Static u-boot environment variables for microdroid. See b/180481192
# Boot the device following the Android boot procedure
-bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0#misc
+# `0` is the disk number of os_composite.img
+# `a` and `_a` are the slot index for A/B
+bootcmd=avb init virtio 0 && avb verify _a && boot_android virtio 0 a
bootdelay=0
fdtaddr=0x80000000
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index cb628b1..30f8481 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,7 +9,7 @@
edition: "2018",
prefer_rlib: true,
rustlibs: [
- "libandroid_logger",
+ "libenv_logger",
"libanyhow",
"libkeystore2_system_property-rust",
"liblog_rust",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index c800002..4f194a3 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -1,4 +1,7 @@
service microdroid_manager /system/bin/microdroid_manager
disabled
+ # TODO(b/189805435) for now redirect stdio to kmsg
+ stdio_to_kmsg
+ setenv RUST_LOG info
# TODO(jooyung) remove this when microdroid_manager becomes a daemon
oneshot
\ No newline at end of file
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index ae72a59..10731c5 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -17,10 +17,9 @@
mod ioutil;
mod metadata;
-use android_logger::Config;
use anyhow::{anyhow, bail, Result};
use keystore2_system_property::PropertyWatcher;
-use log::{info, Level};
+use log::info;
use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
use std::fs;
use std::path::Path;
@@ -29,10 +28,9 @@
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const LOG_TAG: &str = "MicrodroidManager";
-
fn main() -> Result<()> {
- android_logger::init_once(Config::default().with_tag(LOG_TAG).with_min_level(Level::Debug));
+ // TODO(b/189805435) use kernlog
+ env_logger::init();
info!("started.");
@@ -57,8 +55,15 @@
fn exec_task(task: &Task) -> Result<()> {
info!("executing main task {:?}...", task);
- build_command(task)?.spawn()?;
- Ok(())
+ let exit_status = build_command(task)?.spawn()?.wait()?;
+ if exit_status.success() {
+ Ok(())
+ } else {
+ match exit_status.code() {
+ Some(code) => bail!("task exited with exit code: {}", code),
+ None => bail!("task terminated by signal"),
+ }
+ }
}
fn build_command(task: &Task) -> Result<Command> {
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 429b737..8edd65d 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -6,5 +6,9 @@
name: "MicrodroidHostTestCases",
srcs: ["java/**/*.java"],
test_suites: ["device-tests"],
- libs: ["tradefed"],
+ libs: [
+ "tradefed",
+ "compatibility-tradefed",
+ ],
+ data: [":MicrodroidTestApp.signed"],
}
diff --git a/tests/hostside/AndroidTest.xml b/tests/hostside/AndroidTest.xml
index da24b71..247923d 100644
--- a/tests/hostside/AndroidTest.xml
+++ b/tests/hostside/AndroidTest.xml
@@ -18,6 +18,11 @@
<option name="force-root" value="true" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="MicrodroidTestApp.apk" />
+ </target_preparer>
+
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="MicrodroidHostTestCases.jar" />
</test>
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index a1043f7..268ee84 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -20,28 +20,37 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
+import org.json.JSONArray;
+import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
@RunWith(DeviceJUnit4ClassRunner.class)
public class MicrodroidTestCase extends BaseHostJUnit4Test {
@@ -50,134 +59,207 @@
private static final int TEST_VM_CID = 10;
private static final int TEST_VM_ADB_PORT = 8000;
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
+
// This is really slow on GCE (2m 40s) but fast on localhost or actual Android phones (< 10s)
// Set the maximum timeout value big enough.
private static final long MICRODROID_BOOT_TIMEOUT_MINUTES = 5;
- private String executeCommand(String cmd) {
- final long defaultCommandTimeoutMillis = 3000; // 3 sec. Can be slow on GCE
- return executeCommand(defaultCommandTimeoutMillis, cmd);
- }
-
- private String executeCommand(long timeout, String cmd) {
- CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd.split(" "));
- return result.getStdout().trim(); // remove the trailing whitespace including newline
- }
-
- private String executeCommandOnMicrodroid(String cmd) {
- cmd = "adb -s " + MICRODROID_SERIAL + " " + cmd;
- return executeCommand(cmd);
- }
-
@Test
public void testMicrodroidBoots() throws Exception {
- // Prepare input files
- String prepareImagesCmd =
- String.format(
- "mkdir -p %s; cd %s; "
- + "cp %setc/microdroid_bootloader bootloader && "
- + "cp %setc/fs/*.img . && "
- + "cp %setc/uboot_env.img . && "
- + "dd if=/dev/zero of=misc.img bs=4k count=256",
- TEST_ROOT, TEST_ROOT, VIRT_APEX, VIRT_APEX, VIRT_APEX);
- getDevice().executeShellCommand(prepareImagesCmd);
+ final String apkName = "MicrodroidTestApp.apk";
+ final String packageName = "com.android.microdroid.test";
+ final String configPath = "assets/vm_config.json"; // path inside the APK
+ startMicrodroid(apkName, packageName, configPath);
+ waitForMicrodroidBoot(MICRODROID_BOOT_TIMEOUT_MINUTES);
+ adbConnectToMicrodroid();
- // Create os_composite.img, env_composite.img, and payload.img
- String makeOsCompositeCmd =
- String.format(
- "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk.json os_composite.img",
- TEST_ROOT, VIRT_APEX, VIRT_APEX);
- getDevice().executeShellCommand(makeOsCompositeCmd);
- String makeEnvCompositeCmd =
- String.format(
- "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_env.json env_composite.img",
- TEST_ROOT, VIRT_APEX, VIRT_APEX);
- getDevice().executeShellCommand(makeEnvCompositeCmd);
- String makePayloadCompositeCmd =
- String.format(
- "cd %s; %sbin/mk_payload %setc/microdroid_payload.json payload.img",
- TEST_ROOT, VIRT_APEX, VIRT_APEX);
- getDevice().executeShellCommand(makePayloadCompositeCmd);
+ // Check if it actually booted by reading a sysprop.
+ assertThat(runOnMicrodroid("getprop", "ro.hardware"), is("microdroid"));
- // Make sure that the composite images are created
- final List<String> compositeImages =
- new ArrayList<>(
- Arrays.asList(
- TEST_ROOT + "/os_composite.img",
- TEST_ROOT + "/env_composite.img",
- TEST_ROOT + "/payload.img"));
+ // Test writing to /data partition
+ runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
+ assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest"));
+
+ // Check if the APK & its idsig partitions exist
+ final String apkPartition = "/dev/block/by-name/microdroid-apk";
+ assertThat(runOnMicrodroid("ls", apkPartition), is(apkPartition));
+ final String apkIdsigPartition = "/dev/block/by-name/microdroid-apk-idsig";
+ assertThat(runOnMicrodroid("ls", apkIdsigPartition), is(apkIdsigPartition));
+
+ // Check if the APK is mounted using zipfuse
+ final String mountEntry = "zipfuse on /mnt/apk type fuse.zipfuse";
+ assertThat(runOnMicrodroid("mount"), containsString(mountEntry));
+
+ // Check if the native library in the APK is has correct filesystem info
+ final String[] abis = runOnMicrodroid("getprop", "ro.product.cpu.abilist").split(",");
+ assertThat(abis.length, is(1));
+ final String testLib = "/mnt/apk/lib/" + abis[0] + "/MicrodroidTestNativeLib.so";
+ final String label = "u:object_r:system_file:s0";
+ assertThat(runOnMicrodroid("ls", "-Z", testLib), is(label + " " + testLib));
+
+ // Check if the command in vm_config.json was executed by examining the side effect of the
+ // command
+ assertThat(runOnMicrodroid("getprop", "debug.microdroid.app.run"), is("true"));
+
+ // Manually execute the library and check the output
+ final String microdroidLauncher = "system/bin/microdroid_launcher";
+ assertThat(
+ runOnMicrodroid(microdroidLauncher, testLib, "arg1", "arg2"),
+ is("Hello Microdroid " + testLib + " arg1 arg2"));
+
+ // Shutdown microdroid
+ runOnMicrodroid("reboot");
+ }
+
+ // Run an arbitrary command in the host side and returns the result
+ private String runOnHost(String... cmd) {
+ final long timeout = 10000;
+ CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd);
+ assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
+ return result.getStdout().trim();
+ }
+
+ // Same as runOnHost, but failure is not an error
+ private String tryRunOnHost(String... cmd) {
+ final long timeout = 10000;
+ CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd);
+ return result.getStdout().trim();
+ }
+
+ // Run a shell command on Android
+ private String runOnAndroid(String... cmd) throws Exception {
+ CommandResult result = getDevice().executeShellV2Command(join(cmd));
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ fail(join(cmd) + " has failed: " + result);
+ }
+ return result.getStdout().trim();
+ }
+
+ // Same as runOnAndroid, but failutre is not an error
+ private String tryRunOnAndroid(String... cmd) throws Exception {
+ CommandResult result = getDevice().executeShellV2Command(join(cmd));
+ return result.getStdout().trim();
+ }
+
+ // Run a shell command on Microdroid
+ private String runOnMicrodroid(String... cmd) {
+ final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
CommandResult result =
- getDevice().executeShellV2Command("du -b " + String.join(" ", compositeImages));
- assertThat(result.getExitCode(), is(0));
- assertThat(result.getStdout(), is(not("")));
+ RunUtil.getDefault()
+ .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ fail(join(cmd) + " has failed: " + result);
+ }
+ return result.getStdout().trim();
+ }
+
+ private String join(String... strs) {
+ return String.join(" ", Arrays.asList(strs));
+ }
+
+ private String createPayloadImage(String apkName, String packageName, String configPath)
+ throws Exception {
+ File apkFile = findTestFile(apkName);
+ getDevice().installPackage(apkFile, /* reinstall */ true);
+
+ // Read the config file from the apk and parse it to know the list of APEXes needed
+ ZipFile apkAsZip = new ZipFile(apkFile);
+ InputStream is = apkAsZip.getInputStream(apkAsZip.getEntry(configPath));
+ String configString =
+ new BufferedReader(new InputStreamReader(is))
+ .lines()
+ .collect(Collectors.joining("\n"));
+ JSONObject configObject = new JSONObject(configString);
+ JSONArray apexes = configObject.getJSONArray("apexes");
+ List<String> apexNames = new ArrayList<>();
+ for (int i = 0; i < apexes.length(); i++) {
+ JSONObject anApex = apexes.getJSONObject(i);
+ apexNames.add(anApex.getString("name"));
+ }
+
+ // Get the path to the installed apk. Note that
+ // getDevice().getAppPackageInfo(...).getCodePath() doesn't work due to the incorrect
+ // parsing of the "=" character. (b/190975227). So we use the `pm path` command directly.
+ String apkPath = runOnAndroid("pm", "path", packageName);
+ assertTrue(apkPath.startsWith("package:"));
+ apkPath = apkPath.substring("package:".length());
+
+ // Push the idsig file to the device
+ File idsigOnHost = findTestFile(apkName + ".idsig");
+ final String apkIdsigPath = TEST_ROOT + apkName + ".idsig";
+ getDevice().pushFile(idsigOnHost, apkIdsigPath);
+
+ // Create payload.json from the gathered data
+ JSONObject payloadObject = new JSONObject();
+ payloadObject.put("system_apexes", new JSONArray(apexNames));
+ payloadObject.put("payload_config_path", "/mnt/apk/" + configPath);
+ JSONObject apkObject = new JSONObject();
+ apkObject.put("name", packageName);
+ apkObject.put("path", apkPath);
+ apkObject.put("idsig_path", apkIdsigPath);
+ payloadObject.put("apk", apkObject);
+
+ // Copy the json file to Android
+ File payloadJsonOnHost = File.createTempFile("payload", "json");
+ FileWriter writer = new FileWriter(payloadJsonOnHost);
+ writer.write(payloadObject.toString());
+ writer.close();
+ final String payloadJson = TEST_ROOT + "payload.json";
+ getDevice().pushFile(payloadJsonOnHost, payloadJson);
+
+ // Finally run mk_payload to create payload.img
+ final String mkPayload = VIRT_APEX + "bin/mk_payload";
+ final String payloadImg = TEST_ROOT + "payload.img";
+ runOnAndroid(mkPayload, payloadJson, payloadImg);
+ assertThat(runOnAndroid("du", "-b", payloadImg), is(not("")));
+
+ return payloadImg;
+ }
+
+ private File findTestFile(String name) throws Exception {
+ return (new CompatibilityBuildHelper(getBuild())).getTestFile(name);
+ }
+
+ private void startMicrodroid(String apkName, String packageName, String configPath)
+ throws Exception {
+ // Create payload.img
+ final String payloadImg = createPayloadImage(apkName, packageName, configPath);
+
+ // Tools and executables
+ final String mkCdisk = VIRT_APEX + "bin/mk_cdisk";
+ final String crosvm = VIRT_APEX + "bin/crosvm";
+
+ // Create os_composisite.img and env_composite.img
+ // TODO(jiyong): remove this when running a VM is done by `vm`
+ final String cdiskJson = VIRT_APEX + "etc/microdroid_cdisk.json";
+ final String cdiskEnvJson = VIRT_APEX + "etc/microdroid_cdisk_env.json";
+ final String osImg = TEST_ROOT + "os_composite.img";
+ final String envImg = TEST_ROOT + "env_composite.img";
+ final String bootloader = VIRT_APEX + "etc/microdroid_bootloader";
+ runOnAndroid(mkCdisk, cdiskJson, osImg);
+ runOnAndroid(mkCdisk, cdiskEnvJson, envImg);
// Start microdroid using crosvm
+ // TODO(jiyong): do this via the `vm` command
ExecutorService executor = Executors.newFixedThreadPool(1);
- String runMicrodroidCmd =
- String.format(
- "cd %s; %sbin/crosvm run --cid=%d --disable-sandbox --bios=bootloader"
- + " --serial=type=syslog --disk=os_composite.img"
- + " --disk=env_composite.img --disk=payload.img &",
- TEST_ROOT, VIRT_APEX, TEST_VM_CID);
executor.execute(
() -> {
try {
- getDevice().executeShellV2Command(runMicrodroidCmd);
+ runOnAndroid(
+ crosvm,
+ "run",
+ "--cid=" + TEST_VM_CID,
+ "--disable-sandbox",
+ "--bios=" + bootloader,
+ "--serial=type=syslog",
+ "--disk=" + osImg,
+ "--disk=" + envImg,
+ "--disk=" + payloadImg,
+ "&");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
- waitForMicrodroidBoot(MICRODROID_BOOT_TIMEOUT_MINUTES);
-
- // Connect to microdroid and read a system property from there
- executeCommand(
- "adb -s "
- + getDevice().getSerialNumber()
- + " forward tcp:"
- + TEST_VM_ADB_PORT
- + " vsock:"
- + TEST_VM_CID
- + ":5555");
- executeCommand("adb connect " + MICRODROID_SERIAL);
- String prop = executeCommandOnMicrodroid("shell getprop ro.hardware");
- assertThat(prop, is("microdroid"));
-
- // Test writing to /data partition
- File tmpFile = FileUtil.createTempFile("test", ".txt");
- tmpFile.deleteOnExit();
- FileWriter writer = new FileWriter(tmpFile);
- writer.write("MicrodroidTest");
- writer.close();
-
- executeCommandOnMicrodroid("push " + tmpFile.getPath() + " /data/local/tmp/test.txt");
- assertThat(
- executeCommandOnMicrodroid("shell cat /data/local/tmp/test.txt"),
- is("MicrodroidTest"));
-
- assertThat(
- executeCommandOnMicrodroid("shell ls /dev/block/by-name/microdroid-apk"),
- is("/dev/block/by-name/microdroid-apk"));
-
- assertThat(
- executeCommandOnMicrodroid("shell mount"),
- containsString("zipfuse on /mnt/apk type fuse.zipfuse"));
-
- final String[] abiList =
- executeCommandOnMicrodroid("shell getprop ro.product.cpu.abilist").split(",");
- assertThat(abiList.length, is(1));
-
- final String libPath = "/mnt/apk/lib/" + abiList[0] + "/MicrodroidTestNativeLib.so";
- assertThat(
- executeCommandOnMicrodroid("shell ls -Z " + libPath),
- is("u:object_r:system_file:s0 " + libPath));
-
- assertThat(
- executeCommandOnMicrodroid(
- "shell /system/bin/microdroid_launcher " + libPath + " arg1 arg2"),
- is("Hello Microdroid " + libPath + " arg1 arg2"));
-
- // Shutdown microdroid
- executeCommand("adb -s localhost:" + TEST_VM_ADB_PORT + " shell reboot");
}
private void waitForMicrodroidBoot(long timeoutMinutes) throws Exception {
@@ -191,9 +273,19 @@
TimeUnit.MINUTES);
}
+ // Establish an adb connection to microdroid by letting Android forward the connection to
+ // microdroid.
+ private void adbConnectToMicrodroid() {
+ final String serial = getDevice().getSerialNumber();
+ final String from = "tcp:" + TEST_VM_ADB_PORT;
+ final String to = "vsock:" + TEST_VM_CID + ":5555";
+ runOnHost("adb", "-s", serial, "forward", from, to);
+ runOnHost("adb", "connect", MICRODROID_SERIAL);
+ }
+
private void skipIfFail(String command) throws Exception {
- assumeThat(
- getDevice().executeShellV2Command(command).getStatus(), is(CommandStatus.SUCCESS));
+ CommandResult result = getDevice().executeShellV2Command(command);
+ assumeThat(result.getStatus(), is(CommandStatus.SUCCESS));
}
@Before
@@ -208,24 +300,25 @@
@Before
public void setUp() throws Exception {
// kill stale crosvm processes
- getDevice().executeShellV2Command("killall crosvm");
+ tryRunOnAndroid("killall", "crosvm");
- // delete the test root
- getDevice().executeShellCommand("rm -rf " + TEST_ROOT);
+ // Prepare the test root
+ tryRunOnAndroid("rm", "-rf", TEST_ROOT);
+ tryRunOnAndroid("mkdir", "-p", TEST_ROOT);
// disconnect from microdroid
- executeCommand("adb disconnect " + MICRODROID_SERIAL);
+ tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
// clear the log
- getDevice().executeShellV2Command("logcat -c");
+ tryRunOnAndroid("logcat", "-c");
}
@After
public void shutdown() throws Exception {
// disconnect from microdroid
- executeCommand("adb disconnect " + MICRODROID_SERIAL);
+ tryRunOnHost("adb", "disconnect", MICRODROID_SERIAL);
// kill stale crosvm processes
- getDevice().executeShellV2Command("killall crosvm");
+ tryRunOnAndroid("killall", "crosvm");
}
}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 277ccc8..35f2f08 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -7,7 +7,7 @@
srcs: ["src/java/**/*.java"],
jni_libs: ["MicrodroidTestNativeLib"],
sdk_version: "current",
- apex_available: ["com.android.virt"], // TODO(jiyong): remove this from virt APEX
+ use_embedded_native_libs: true,
}
// TODO(jiyong): make this a binary, not a shared library
@@ -15,5 +15,21 @@
name: "MicrodroidTestNativeLib",
srcs: ["src/native/*.cpp"],
sdk_version: "current",
- apex_available: ["com.android.virt"], // TODO(jiyong): remove this from virt APEX
+}
+
+genrule {
+ name: "MicrodroidTestApp.signed",
+ out: [
+ "MicrodroidTestApp.apk",
+ "MicrodroidTestApp.apk.idsig",
+ ],
+ srcs: [":MicrodroidTestApp"],
+ tools:["apksigner"],
+ tool_files: ["test.keystore"],
+ cmd: "$(location apksigner) sign " +
+ "--ks $(location test.keystore) " +
+ "--ks-pass=pass:testkey --key-pass=pass:testkey " +
+ "--in $(in) " +
+ "--out $(genDir)/MicrodroidTestApp.apk",
+ // $(genDir)/MicrodroidTestApp.apk.idsig is generated implicitly
}
diff --git a/tests/testapk/assets/vm_config.json b/tests/testapk/assets/vm_config.json
index 7a3df7a..8312f4d 100644
--- a/tests/testapk/assets/vm_config.json
+++ b/tests/testapk/assets/vm_config.json
@@ -9,5 +9,19 @@
"hello",
"microdroid"
]
- }
-}
\ No newline at end of file
+ },
+ "apexes": [
+ {
+ "name": "com.android.adbd"
+ },
+ {
+ "name": "com.android.i18n"
+ },
+ {
+ "name": "com.android.os.statsd"
+ },
+ {
+ "name": "com.android.sdkext"
+ }
+ ]
+}
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index c317cd2..c3eefc4 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <stdio.h>
+#include <sys/system_properties.h>
extern "C" int android_native_main(int argc, char* argv[]) {
printf("Hello Microdroid ");
@@ -25,5 +26,7 @@
}
}
printf("\n");
+
+ __system_property_set("debug.microdroid.app.run", "true");
return 0;
}
diff --git a/tests/testapk/test.keystore b/tests/testapk/test.keystore
new file mode 100644
index 0000000..2f024d8
--- /dev/null
+++ b/tests/testapk/test.keystore
Binary files differ
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index bad7f46..f5ad1f8 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -7,6 +7,17 @@
crate_name: "virtualizationservice",
srcs: ["src/main.rs"],
edition: "2018",
+ // Only build on targets which crosvm builds on.
+ enabled: false,
+ target: {
+ android64: {
+ compile_multilib: "64",
+ enabled: true,
+ },
+ linux_bionic_arm64: {
+ enabled: true,
+ },
+ },
prefer_rlib: true,
rustlibs: [
"android.system.virtualizationservice-rust",
@@ -14,6 +25,7 @@
"libanyhow",
"libcommand_fds",
"libcompositediskconfig",
+ "libdisk",
"liblog_rust",
"libserde_json",
"libserde",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 311c2e0..8affaad 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -27,7 +27,11 @@
IVirtualMachine startVm(
in VirtualMachineConfig config, in @nullable ParcelFileDescriptor logFd);
- /** Initialise an empty partition image of the given size to be used as a writable partition. */
+ /**
+ * Initialise an empty partition image of the given size to be used as a writable partition.
+ *
+ * The file must be open with both read and write permissions, and should be a new empty file.
+ */
void initializeWritablePartition(in ParcelFileDescriptor imageFd, long size);
/**
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7b63917..6d3f737 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -26,13 +26,14 @@
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig;
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo;
use android_system_virtualizationservice::binder::{
- self, BinderFeatures, Interface, ParcelFileDescriptor, StatusCode, Strong, ThreadState,
+ self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Status, Strong, ThreadState,
};
use command_fds::FdMapping;
+use disk::QcowFile;
use log::{debug, error, warn};
use std::convert::TryInto;
+use std::ffi::CString;
use std::fs::{File, create_dir};
-use std::io::{Seek, SeekFrom, Write};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
@@ -80,10 +81,16 @@
let temporary_directory: PathBuf = format!("{}/{}", TEMPORARY_DIRECTORY, cid).into();
create_dir(&temporary_directory).map_err(|e| {
error!(
- "Failed to create temporary directory {:?} for VM files: {:?}",
+ "Failed to create temporary directory {:?} for VM files: {}",
temporary_directory, e
);
- StatusCode::UNKNOWN_ERROR
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!(
+ "Failed to create temporary directory {:?} for VM files: {}",
+ temporary_directory, e
+ ),
+ )
})?;
// Assemble disk images if needed.
@@ -126,8 +133,11 @@
requester_debug_pid,
)
.map_err(|e| {
- error!("Failed to start VM with config {:?}: {:?}", config, e);
- StatusCode::UNKNOWN_ERROR
+ error!("Failed to start VM with config {:?}: {}", config, e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to start VM: {}", e),
+ )
})?;
state.add_vm(Arc::downgrade(&instance));
Ok(VirtualMachine::create(instance))
@@ -139,26 +149,20 @@
image_fd: &ParcelFileDescriptor,
size: i64,
) -> binder::Result<()> {
- let size: u64 = size.try_into().map_err(|e| {
- error!("Invalid size {}: {}", size, e);
- StatusCode::BAD_VALUE
+ let size = size.try_into().map_err(|e| {
+ new_binder_exception(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ format!("Invalid size {}: {}", size, e),
+ )
})?;
- let mut image = clone_file(image_fd)?;
+ let image = clone_file(image_fd)?;
- // TODO: create a QCOW2 image instead, like `crosvm create_qcow2`, once `mk_cdisk` supports
- // it (b/189211641).
- if size > 0 {
- // Extend the file to the given size by seeking to the size we want and writing a single
- // 0 byte there.
- image.seek(SeekFrom::Start(size - 1)).map_err(|e| {
- error!("Failed to seek to desired size of image file ({}): {}.", size, e);
- StatusCode::UNKNOWN_ERROR
- })?;
- image.write_all(&[0]).map_err(|e| {
- error!("Failed to write 0 to image file: {}.", e);
- StatusCode::UNKNOWN_ERROR
- })?;
- }
+ QcowFile::new(image, size).map_err(|e| {
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to create QCOW2 image: {}", e),
+ )
+ })?;
Ok(())
}
@@ -166,9 +170,7 @@
/// Get a list of all currently running VMs. This method is only intended for debug purposes,
/// and as such is only permitted from the shell user.
fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
- if !debug_access_allowed() {
- return Err(StatusCode::PERMISSION_DENIED.into());
- }
+ check_debug_access()?;
let state = &mut *self.state.lock().unwrap();
let vms = state.vms();
@@ -189,9 +191,7 @@
/// Hold a strong reference to a VM in VirtualizationService. This method is only intended for
/// debug purposes, and as such is only permitted from the shell user.
fn debugHoldVmRef(&self, vmref: &Strong<dyn IVirtualMachine>) -> binder::Result<()> {
- if !debug_access_allowed() {
- return Err(StatusCode::PERMISSION_DENIED.into());
- }
+ check_debug_access()?;
let state = &mut *self.state.lock().unwrap();
state.debug_hold_vm(vmref.clone());
@@ -202,9 +202,7 @@
/// the VM was found and None otherwise. This method is only intended for debug purposes, and as
/// such is only permitted from the shell user.
fn debugDropVmRef(&self, cid: i32) -> binder::Result<Option<Strong<dyn IVirtualMachine>>> {
- if !debug_access_allowed() {
- return Err(StatusCode::PERMISSION_DENIED.into());
- }
+ check_debug_access()?;
let state = &mut *self.state.lock().unwrap();
Ok(state.debug_drop_vm(cid))
@@ -219,19 +217,25 @@
temporary_directory: &Path,
next_temporary_image_id: &mut u64,
indirect_files: &mut Vec<File>,
-) -> Result<DiskFile, StatusCode> {
+) -> Result<DiskFile, Status> {
let image = if !disk.partitions.is_empty() {
if disk.image.is_some() {
warn!("DiskImage {:?} contains both image and partitions.", disk);
- return Err(StatusCode::BAD_VALUE);
+ return Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ "DiskImage contains both image and partitions.",
+ ));
}
let composite_image_filename =
make_composite_image_filename(temporary_directory, next_temporary_image_id);
let (image, partition_files) =
make_composite_image(&disk.partitions, &composite_image_filename).map_err(|e| {
- error!("Failed to make composite image with config {:?}: {:?}", disk, e);
- StatusCode::UNKNOWN_ERROR
+ error!("Failed to make composite image with config {:?}: {}", disk, e);
+ new_binder_exception(
+ ExceptionCode::SERVICE_SPECIFIC,
+ format!("Failed to make composite image: {}", e),
+ )
})?;
// Pass the file descriptors for the various partition files to crosvm when it
@@ -243,7 +247,10 @@
clone_file(image)?
} else {
warn!("DiskImage {:?} didn't contain image or partitions.", disk);
- return Err(StatusCode::BAD_VALUE);
+ return Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ "DiskImage didn't contain image or partitions.",
+ ));
};
Ok(DiskFile { image, writable: disk.writable })
@@ -260,28 +267,35 @@
}
/// Gets the calling SID of the current Binder thread.
-fn get_calling_sid() -> Result<String, StatusCode> {
+fn get_calling_sid() -> Result<String, Status> {
ThreadState::with_calling_sid(|sid| {
if let Some(sid) = sid {
match sid.to_str() {
Ok(sid) => Ok(sid.to_owned()),
Err(e) => {
- error!("SID was not valid UTF-8: {:?}", e);
- Err(StatusCode::BAD_VALUE)
+ error!("SID was not valid UTF-8: {}", e);
+ Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ format!("SID was not valid UTF-8: {}", e),
+ ))
}
}
} else {
error!("Missing SID on startVm");
- Err(StatusCode::UNKNOWN_ERROR)
+ Err(new_binder_exception(ExceptionCode::SECURITY, "Missing SID on startVm"))
}
})
}
/// Check whether the caller of the current Binder method is allowed to call debug methods.
-fn debug_access_allowed() -> bool {
+fn check_debug_access() -> binder::Result<()> {
let uid = ThreadState::get_calling_uid();
log::trace!("Debug method call from UID {}.", uid);
- DEBUG_ALLOWED_UIDS.contains(&uid)
+ if DEBUG_ALLOWED_UIDS.contains(&uid) {
+ Ok(())
+ } else {
+ Err(new_binder_exception(ExceptionCode::SECURITY, "Debug access denied"))
+ }
}
/// Implementation of the AIDL `IVirtualMachine` interface. Used as a handle to a VM.
@@ -396,7 +410,7 @@
fn allocate_cid(&mut self) -> binder::Result<Cid> {
// TODO(qwandor): keep track of which CIDs are currently in use so that we can reuse them.
let cid = self.next_cid;
- self.next_cid = self.next_cid.checked_add(1).ok_or(StatusCode::UNKNOWN_ERROR)?;
+ self.next_cid = self.next_cid.checked_add(1).ok_or(ExceptionCode::ILLEGAL_STATE)?;
Ok(cid)
}
}
@@ -413,6 +427,16 @@
}
/// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
-fn clone_file(file: &ParcelFileDescriptor) -> Result<File, StatusCode> {
- file.as_ref().try_clone().map_err(|_| StatusCode::UNKNOWN_ERROR)
+fn clone_file(file: &ParcelFileDescriptor) -> Result<File, Status> {
+ file.as_ref().try_clone().map_err(|e| {
+ new_binder_exception(
+ ExceptionCode::BAD_PARCELABLE,
+ format!("Failed to clone File from ParcelFileDescriptor: {}", e),
+ )
+ })
+}
+
+/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
+fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+ Status::new_exception(exception, CString::new(message.as_ref()).ok().as_deref())
}
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 138236c..797011c 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -133,7 +133,7 @@
// Delete temporary files.
if let Err(e) = remove_dir_all(&self.temporary_directory) {
- error!("Error removing temporary directory {:?}: {:?}", self.temporary_directory, e);
+ error!("Error removing temporary directory {:?}: {}", self.temporary_directory, e);
}
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index e2c11a8..2c93ec4 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -104,6 +104,7 @@
) -> Result<(), Error> {
let image = OpenOptions::new()
.create_new(true)
+ .read(true)
.write(true)
.open(image_path)
.with_context(|| format!("Failed to create {:?}", image_path))?;
diff --git a/zipfuse/zipfuse.rc b/zipfuse/zipfuse.rc
index ccd94b6..1905705 100644
--- a/zipfuse/zipfuse.rc
+++ b/zipfuse/zipfuse.rc
@@ -1,2 +1,2 @@
-service zipfuse /system/bin/zipfuse -o fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0 /dev/block/by-name/microdroid-apk /mnt/apk
+service zipfuse /system/bin/zipfuse -o fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0 /dev/block/mapper/microdroid-apk /mnt/apk
disabled