Merge "Make exporting tombstone (out of VM) configurable"
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index e782bd2..a1e81d2 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -16,22 +16,63 @@
"""sign_virt_apex is a command line tool for sign the Virt APEX file.
Typical usage:
- sign_virt_apex [-v] [--avbtool path_to_avbtool] [--signing_args args] payload_key payload_dir
+ sign_virt_apex payload_key payload_dir
+ -v, --verbose
+ --verify
+ --avbtool path_to_avbtool
+ --signing_args args
sign_virt_apex uses external tools which are assumed to be available via PATH.
- avbtool (--avbtool can override the tool)
- lpmake, lpunpack, simg2img, img2simg
"""
import argparse
-import glob
import hashlib
import os
import re
import shlex
-import shutil
import subprocess
import sys
import tempfile
+import traceback
+from concurrent import futures
+
+# pylint: disable=line-too-long,consider-using-with
+
+# Use executor to parallelize the invocation of external tools
+# If a task depends on another, pass the future object of the previous task as wait list.
+# Every future object created by a task should be consumed with AwaitAll()
+# so that exceptions are propagated .
+executor = futures.ThreadPoolExecutor()
+
+# Temporary directory for unpacked super.img.
+# We could put its creation/deletion into the task graph as well, but
+# having it as a global setup is much simpler.
+unpack_dir = tempfile.TemporaryDirectory()
+
+# tasks created with Async() are kept in a list so that they are awaited
+# before exit.
+tasks = []
+
+# create an async task and return a future value of it.
+def Async(fn, *args, wait=None, **kwargs):
+
+ # wrap a function with AwaitAll()
+ def wrapped():
+ AwaitAll(wait)
+ fn(*args, **kwargs)
+
+ task = executor.submit(wrapped)
+ tasks.append(task)
+ return task
+
+
+# waits for task (captured in fs as future values) with future.result()
+# so that any exception raised during task can be raised upward.
+def AwaitAll(fs):
+ if fs:
+ for f in fs:
+ f.result()
def ParseArgs(argv):
@@ -71,7 +112,8 @@
return args
-def RunCommand(args, cmd, env=None, expected_return_values={0}):
+def RunCommand(args, cmd, env=None, expected_return_values=None):
+ expected_return_values = expected_return_values or {0}
env = env or {}
env.update(os.environ.copy())
@@ -218,7 +260,7 @@
if info is None:
return
- with TempDirectory() as work_dir:
+ with tempfile.TemporaryDirectory() as work_dir:
algorithm = info['Algorithm']
rollback_index = info['Rollback Index']
rollback_index_location = info['Rollback Index Location']
@@ -254,18 +296,14 @@
f.truncate(65536)
-class TempDirectory(object):
-
- def __enter__(self):
- self.name = tempfile.mkdtemp()
- return self.name
-
- def __exit__(self, *unused):
- shutil.rmtree(self.name)
+def UnpackSuperImg(args, super_img, work_dir):
+ tmp_super_img = os.path.join(work_dir, 'super.img')
+ RunCommand(args, ['simg2img', super_img, tmp_super_img])
+ RunCommand(args, ['lpunpack', tmp_super_img, work_dir])
def MakeSuperImage(args, partitions, output):
- with TempDirectory() as work_dir:
+ with tempfile.TemporaryDirectory() as work_dir:
cmd = ['lpmake', '--device-size=auto', '--metadata-slots=2', # A/B
'--metadata-size=65536', '--sparse', '--output=' + output]
@@ -281,6 +319,22 @@
RunCommand(args, cmd)
+def SignSuperImg(args, key, super_img, work_dir):
+ # unpack super.img
+ UnpackSuperImg(args, super_img, work_dir)
+
+ system_a_img = os.path.join(work_dir, 'system_a.img')
+ vendor_a_img = os.path.join(work_dir, 'vendor_a.img')
+
+ # re-sign each partition
+ system_a_f = Async(AddHashTreeFooter, args, key, system_a_img)
+ vendor_a_f = Async(AddHashTreeFooter, args, key, vendor_a_img)
+
+ # 3. re-pack super.img
+ partitions = {"system_a": system_a_img, "vendor_a": vendor_a_img}
+ Async(MakeSuperImage, args, partitions, super_img, wait=[system_a_f, vendor_a_f])
+
+
def ReplaceBootloaderPubkey(args, key, bootloader, bootloader_pubkey):
if os.path.basename(bootloader) in args.key_overrides:
key = args.key_overrides[os.path.basename(bootloader)]
@@ -305,134 +359,116 @@
bl_f.write(new_pubkey)
+# dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
+virt_apex_files = {
+ 'bootloader.pubkey': 'etc/microdroid_bootloader.avbpubkey',
+ 'bootloader': 'etc/microdroid_bootloader',
+ 'boot.img': 'etc/fs/microdroid_boot-5.10.img',
+ 'vendor_boot.img': 'etc/fs/microdroid_vendor_boot-5.10.img',
+ 'init_boot.img': 'etc/fs/microdroid_init_boot.img',
+ 'super.img': 'etc/fs/microdroid_super.img',
+ 'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
+ 'vbmeta_bootconfig.img': 'etc/fs/microdroid_vbmeta_bootconfig.img',
+ 'bootconfig.normal': 'etc/microdroid_bootconfig.normal',
+ 'bootconfig.app_debuggable': 'etc/microdroid_bootconfig.app_debuggable',
+ 'bootconfig.full_debuggable': 'etc/microdroid_bootconfig.full_debuggable',
+ 'uboot_env.img': 'etc/uboot_env.img'
+}
+
+
+def TargetFiles(input_dir):
+ return {k: os.path.join(input_dir, v) for k, v in virt_apex_files.items()}
+
+
def SignVirtApex(args):
key = args.key
input_dir = args.input_dir
+ files = TargetFiles(input_dir)
- # target files in the Virt APEX
- bootloader_pubkey = os.path.join(
- input_dir, 'etc', 'microdroid_bootloader.avbpubkey')
- bootloader = os.path.join(input_dir, 'etc', 'microdroid_bootloader')
- boot_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_boot-5.10.img')
- vendor_boot_img = os.path.join(
- input_dir, 'etc', 'fs', 'microdroid_vendor_boot-5.10.img')
- init_boot_img = os.path.join(
- input_dir, 'etc', 'fs', 'microdroid_init_boot.img')
- super_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_super.img')
- vbmeta_img = os.path.join(input_dir, 'etc', 'fs', 'microdroid_vbmeta.img')
- vbmeta_bootconfig_img = os.path.join(
- input_dir, 'etc', 'fs', 'microdroid_vbmeta_bootconfig.img')
- bootconfig_normal = os.path.join(
- input_dir, 'etc', 'microdroid_bootconfig.normal')
- bootconfig_app_debuggable = os.path.join(
- input_dir, 'etc', 'microdroid_bootconfig.app_debuggable')
- bootconfig_full_debuggable = os.path.join(
- input_dir, 'etc', 'microdroid_bootconfig.full_debuggable')
- uboot_env_img = os.path.join(
- input_dir, 'etc', 'uboot_env.img')
+ # unpacked files (will be unpacked from super.img below)
+ system_a_img = os.path.join(unpack_dir.name, 'system_a.img')
+ vendor_a_img = os.path.join(unpack_dir.name, 'vendor_a.img')
- # Key(pubkey) for bootloader should match with the one used to make VBmeta below
+ # Key(pubkey) embedded in bootloader should match with the one used to make VBmeta below
# while it's okay to use different keys for other image files.
- ReplaceBootloaderPubkey(args, key, bootloader, bootloader_pubkey)
+ replace_f = Async(ReplaceBootloaderPubkey, args,
+ key, files['bootloader'], files['bootloader.pubkey'])
# re-sign bootloader, boot.img, vendor_boot.img, and init_boot.img
- AddHashFooter(args, key, bootloader)
- AddHashFooter(args, key, boot_img)
- AddHashFooter(args, key, vendor_boot_img)
- AddHashFooter(args, key, init_boot_img)
+ Async(AddHashFooter, args, key, files['bootloader'], wait=[replace_f])
+ boot_img_f = Async(AddHashFooter, args, key, files['boot.img'])
+ vendor_boot_img_f = Async(AddHashFooter, args, key, files['vendor_boot.img'])
+ init_boot_img_f = Async(AddHashFooter, args, key, files['init_boot.img'])
# re-sign super.img
- with TempDirectory() as work_dir:
- # unpack super.img
- tmp_super_img = os.path.join(work_dir, 'super.img')
- RunCommand(args, ['simg2img', super_img, tmp_super_img])
- RunCommand(args, ['lpunpack', tmp_super_img, work_dir])
+ super_img_f = Async(SignSuperImg, args, key, files['super.img'], unpack_dir.name)
- system_a_img = os.path.join(work_dir, 'system_a.img')
- vendor_a_img = os.path.join(work_dir, 'vendor_a.img')
- partitions = {"system_a": system_a_img, "vendor_a": vendor_a_img}
-
- # re-sign partitions in super.img
- for img in partitions.values():
- AddHashTreeFooter(args, key, img)
-
- # re-pack super.img
- MakeSuperImage(args, partitions, super_img)
-
- # re-generate vbmeta from re-signed {boot, vendor_boot, init_boot, system_a, vendor_a}.img
- # Ideally, making VBmeta should be done out of TempDirectory block. But doing it here
- # to avoid unpacking re-signed super.img for system/vendor images which are available
- # in this block.
- MakeVbmetaImage(args, key, vbmeta_img, images=[
- boot_img, vendor_boot_img, init_boot_img, system_a_img, vendor_a_img])
+ # re-generate vbmeta from re-signed {boot, vendor_boot, init_boot, system_a, vendor_a}.img
+ Async(MakeVbmetaImage, args, key, files['vbmeta.img'],
+ images=[files['boot.img'], files['vendor_boot.img'],
+ files['init_boot.img'], system_a_img, vendor_a_img],
+ wait=[boot_img_f, vendor_boot_img_f, init_boot_img_f, super_img_f])
# Re-sign bootconfigs and the uboot_env with the same key
bootconfig_sign_key = key
- AddHashFooter(args, bootconfig_sign_key, bootconfig_normal)
- AddHashFooter(args, bootconfig_sign_key, bootconfig_app_debuggable)
- AddHashFooter(args, bootconfig_sign_key, bootconfig_full_debuggable)
- AddHashFooter(args, bootconfig_sign_key, uboot_env_img)
+ Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.normal'])
+ Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.app_debuggable'])
+ Async(AddHashFooter, args, bootconfig_sign_key, files['bootconfig.full_debuggable'])
+ Async(AddHashFooter, args, bootconfig_sign_key, files['uboot_env.img'])
# Re-sign vbmeta_bootconfig with chained_partitions to "bootconfig" and
# "uboot_env". Note that, for now, `key` and `bootconfig_sign_key` are the
# same, but technically they can be different. Vbmeta records pubkeys which
# signed chained partitions.
- MakeVbmetaImage(args, key, vbmeta_bootconfig_img, chained_partitions={
- 'bootconfig': bootconfig_sign_key,
- 'uboot_env': bootconfig_sign_key,
+ Async(MakeVbmetaImage, args, key, files['vbmeta_bootconfig.img'], chained_partitions={
+ 'bootconfig': bootconfig_sign_key,
+ 'uboot_env': bootconfig_sign_key,
})
def VerifyVirtApex(args):
- # Generator to emit avbtool-signed items along with its pubkey digest.
- # This supports lpmake-packed images as well.
- def Recur(target_dir):
- for file in glob.glob(os.path.join(target_dir, 'etc', '**', '*'), recursive=True):
- cur_item = os.path.relpath(file, target_dir)
+ key = args.key
+ input_dir = args.input_dir
+ files = TargetFiles(input_dir)
- if not os.path.isfile(file):
- continue
+ # unpacked files
+ UnpackSuperImg(args, files['super.img'], unpack_dir.name)
+ system_a_img = os.path.join(unpack_dir.name, 'system_a.img')
+ vendor_a_img = os.path.join(unpack_dir.name, 'vendor_a.img')
- # avbpubkey
- if cur_item == 'etc/microdroid_bootloader.avbpubkey':
- with open(file, 'rb') as f:
- yield (cur_item, hashlib.sha1(f.read()).hexdigest())
- continue
+ # Read pubkey digest from the input key
+ with tempfile.NamedTemporaryFile() as pubkey_file:
+ ExtractAvbPubkey(args, key, pubkey_file.name)
+ with open(pubkey_file.name, 'rb') as f:
+ pubkey = f.read()
+ pubkey_digest = hashlib.sha1(pubkey).hexdigest()
- # avbtool signed
- info, _ = AvbInfo(args, file)
- if info:
- yield (cur_item, info['Public key (sha1)'])
- continue
+ def contents(file):
+ with open(file, 'rb') as f:
+ return f.read()
- # logical partition
- with TempDirectory() as tmp_dir:
- unsparsed = os.path.join(tmp_dir, os.path.basename(file))
- _, rc = RunCommand(
- # exit with 255 if it's not sparsed
- args, ['simg2img', file, unsparsed], expected_return_values={0, 255})
- if rc == 0:
- with TempDirectory() as unpack_dir:
- # exit with 64 if it's not a logical partition.
- _, rc = RunCommand(
- args, ['lpunpack', unsparsed, unpack_dir], expected_return_values={0, 64})
- if rc == 0:
- nested_items = list(Recur(unpack_dir))
- if len(nested_items) > 0:
- for (item, key) in nested_items:
- yield ('%s!/%s' % (cur_item, item), key)
- continue
- # Read pubkey digest
- with TempDirectory() as tmp_dir:
- pubkey_file = os.path.join(tmp_dir, 'avbpubkey')
- ExtractAvbPubkey(args, args.key, pubkey_file)
- with open(pubkey_file, 'rb') as f:
- pubkey_digest = hashlib.sha1(f.read()).hexdigest()
+ def check_equals_pubkey(file):
+ assert contents(file) == pubkey, 'pubkey mismatch: %s' % file
- # Check every avbtool-signed item against the input key
- for (item, pubkey) in Recur(args.input_dir):
- assert pubkey == pubkey_digest, '%s: key mismatch: %s != %s' % (
- item, pubkey, pubkey_digest)
+ def check_contains_pubkey(file):
+ assert contents(file).find(pubkey) != -1, 'pubkey missing: %s' % file
+
+ def check_avb_pubkey(file):
+ info, _ = AvbInfo(args, file)
+ assert info is not None, 'no avbinfo: %s' % file
+ assert info['Public key (sha1)'] == pubkey_digest, 'pubkey mismatch: %s' % file
+
+ for f in files.values():
+ if f == files['bootloader.pubkey']:
+ Async(check_equals_pubkey, f)
+ elif f == files['bootloader']:
+ Async(check_contains_pubkey, f)
+ elif f == files['super.img']:
+ Async(check_avb_pubkey, system_a_img)
+ Async(check_avb_pubkey, vendor_a_img)
+ else:
+ # Check pubkey for other files using avbtool
+ Async(check_avb_pubkey, f)
def main(argv):
@@ -442,8 +478,10 @@
VerifyVirtApex(args)
else:
SignVirtApex(args)
- except Exception as e:
- print(e)
+ # ensure all tasks are completed without exceptions
+ AwaitAll(tasks)
+ except: # pylint: disable=bare-except
+ traceback.print_exc()
sys.exit(1)
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index 7e71105..02b2081 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -14,8 +14,8 @@
service virtualizationservice /apex/com.android.virt/bin/virtualizationservice
class main
- user virtualizationservice
- group virtualizationservice
+ user system
+ group system
interface aidl android.system.virtualizationservice
disabled
oneshot
diff --git a/apkdmverity/src/loopdevice.rs b/apkdmverity/src/loopdevice.rs
index 376abd4..35ae154 100644
--- a/apkdmverity/src/loopdevice.rs
+++ b/apkdmverity/src/loopdevice.rs
@@ -25,8 +25,10 @@
use anyhow::{Context, Result};
use data_model::DataInit;
+use libc::O_DIRECT;
use std::fs::{File, OpenOptions};
use std::mem::size_of;
+use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::thread;
@@ -37,8 +39,7 @@
// These are old-style ioctls, thus *_bad.
nix::ioctl_none_bad!(_loop_ctl_get_free, LOOP_CTL_GET_FREE);
-nix::ioctl_write_int_bad!(_loop_set_fd, LOOP_SET_FD);
-nix::ioctl_write_ptr_bad!(_loop_set_status64, LOOP_SET_STATUS64, loop_info64);
+nix::ioctl_write_ptr_bad!(_loop_configure, LOOP_CONFIGURE, loop_config);
#[cfg(test)]
nix::ioctl_none_bad!(_loop_clr_fd, LOOP_CLR_FD);
@@ -49,14 +50,9 @@
Ok(unsafe { _loop_ctl_get_free(ctrl_file.as_raw_fd()) }?)
}
-fn loop_set_fd(device_file: &File, fd: i32) -> Result<i32> {
+fn loop_configure(device_file: &File, config: &loop_config) -> Result<i32> {
// SAFETY: this ioctl changes the state in kernel, but not the state in this process.
- 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) }?)
+ Ok(unsafe { _loop_configure(device_file.as_raw_fd(), config) }?)
}
#[cfg(test)]
@@ -67,7 +63,12 @@
}
/// Creates a loop device and attach the given file at `path` as the backing store.
-pub fn attach<P: AsRef<Path>>(path: P, offset: u64, size_limit: u64) -> Result<PathBuf> {
+pub fn attach<P: AsRef<Path>>(
+ path: P,
+ offset: u64,
+ size_limit: u64,
+ direct_io: bool,
+) -> Result<PathBuf> {
// Attaching a file to a loop device can make a race condition; a loop device number obtained
// from LOOP_CTL_GET_FREE might have been used by another thread or process. In that case the
// subsequet LOOP_CONFIGURE ioctl returns with EBUSY. Try until it succeeds.
@@ -81,7 +82,7 @@
let begin = Instant::now();
loop {
- match try_attach(&path, offset, size_limit) {
+ match try_attach(&path, offset, size_limit, direct_io) {
Ok(loop_dev) => return Ok(loop_dev),
Err(e) => {
if begin.elapsed() > TIMEOUT {
@@ -99,7 +100,12 @@
#[cfg(target_os = "android")]
const LOOP_DEV_PREFIX: &str = "/dev/block/loop";
-fn try_attach<P: AsRef<Path>>(path: P, offset: u64, size_limit: u64) -> Result<PathBuf> {
+fn try_attach<P: AsRef<Path>>(
+ path: P,
+ offset: u64,
+ size_limit: u64,
+ direct_io: bool,
+) -> Result<PathBuf> {
// Get a free loop device
wait_for_path(LOOP_CONTROL)?;
let ctrl_file = OpenOptions::new()
@@ -112,21 +118,19 @@
// Construct the loop_info64 struct
let backing_file = OpenOptions::new()
.read(true)
+ .custom_flags(if direct_io { O_DIRECT } else { 0 })
.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 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) {
- info.lo_flags.remove(Flag::LO_FLAGS_DIRECT_IO);
+ 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_READ_ONLY;
+ if direct_io {
+ config.info.lo_flags.insert(Flag::LO_FLAGS_DIRECT_IO);
}
// Configure the loop device to attach the backing file
@@ -137,8 +141,8 @@
.write(true)
.open(&device_path)
.context(format!("failed to open {:?}", &device_path))?;
- loop_set_fd(&device_file, backing_file.as_raw_fd() as i32)?;
- loop_set_status64(&device_file, &info)?;
+ loop_configure(&device_file, &config)
+ .context(format!("Failed to configure {:?}", &device_path))?;
Ok(PathBuf::from(device_path))
}
@@ -150,3 +154,46 @@
loop_clr_fd(&device_file)?;
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs;
+ use std::path::Path;
+
+ fn create_empty_file(path: &Path, size: u64) {
+ let f = File::create(path).unwrap();
+ f.set_len(size).unwrap();
+ }
+
+ fn is_direct_io(dev: &Path) -> bool {
+ let dio = Path::new("/sys/block").join(dev.file_name().unwrap()).join("loop/dio");
+ "1" == fs::read_to_string(&dio).unwrap().trim()
+ }
+
+ #[test]
+ fn attach_loop_device_with_dio() {
+ let a_dir = tempfile::TempDir::new().unwrap();
+ let a_file = a_dir.path().join("test");
+ let a_size = 4096u64;
+ create_empty_file(&a_file, a_size);
+ let dev = attach(a_file, 0, a_size, /*direct_io*/ true).unwrap();
+ scopeguard::defer! {
+ detach(&dev).unwrap();
+ }
+ assert!(is_direct_io(&dev));
+ }
+
+ #[test]
+ fn attach_loop_device_without_dio() {
+ let a_dir = tempfile::TempDir::new().unwrap();
+ let a_file = a_dir.path().join("test");
+ let a_size = 4096u64;
+ create_empty_file(&a_file, a_size);
+ let dev = attach(a_file, 0, a_size, /*direct_io*/ false).unwrap();
+ scopeguard::defer! {
+ detach(&dev).unwrap();
+ }
+ assert!(!is_direct_io(&dev));
+ }
+}
diff --git a/apkdmverity/src/loopdevice/sys.rs b/apkdmverity/src/loopdevice/sys.rs
index d32987a..fa87548 100644
--- a/apkdmverity/src/loopdevice/sys.rs
+++ b/apkdmverity/src/loopdevice/sys.rs
@@ -24,8 +24,7 @@
pub const LOOP_CONTROL: &str = "/dev/loop-control";
pub const LOOP_CTL_GET_FREE: libc::c_ulong = 0x4C82;
-pub const LOOP_SET_FD: libc::c_ulong = 0x4C00;
-pub const LOOP_SET_STATUS64: libc::c_ulong = 0x4C04;
+pub const LOOP_CONFIGURE: libc::c_ulong = 0x4C0A;
#[cfg(test)]
pub const LOOP_CLR_FD: libc::c_ulong = 0x4C01;
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index dbf3131..16dd480 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -94,7 +94,11 @@
if apk_size % BLOCK_SIZE != 0 {
bail!("The size of {:?} is not multiple of {}.", &apk, BLOCK_SIZE)
}
- (loopdevice::attach(&apk, 0, apk_size)?, apk_size)
+ (
+ loopdevice::attach(&apk, 0, apk_size, /*direct_io*/ true)
+ .context("Failed to attach APK to a loop device")?,
+ apk_size,
+ )
};
// Parse the idsig file to locate the merkle tree in it, then attach the file to a loop device
@@ -105,7 +109,11 @@
)?;
let offset = sig.merkle_tree_offset;
let size = sig.merkle_tree_size as u64;
- let hash_device = loopdevice::attach(&idsig, offset, size)?;
+ // Due to unknown reason(b/191344832), we can't enable "direct IO" for the IDSIG file (backing
+ // the hash). For now we don't use "direct IO" but it seems OK since the IDSIG file is very
+ // small and the benefit of direct-IO would be negliable.
+ let hash_device = loopdevice::attach(&idsig, offset, size, /*direct_io*/ false)
+ .context("Failed to attach idsig to a loop device")?;
// Build a dm-verity target spec from the information from the idsig file. The apk and the
// idsig files are used as the data device and the hash device, respectively.
@@ -318,11 +326,11 @@
// already a block device, `enable_verity` uses the block device as it is. The detatching
// of the data device is done in the scopeguard for the return value of `enable_verity`
// below. Only the idsig_loop_device needs detatching.
- let apk_loop_device = loopdevice::attach(&apk_path, 0, apk_size).unwrap();
- let idsig_loop_device =
- scopeguard::guard(loopdevice::attach(&idsig_path, 0, idsig_size).unwrap(), |dev| {
- loopdevice::detach(dev).unwrap()
- });
+ let apk_loop_device = loopdevice::attach(&apk_path, 0, apk_size, true).unwrap();
+ let idsig_loop_device = scopeguard::guard(
+ loopdevice::attach(&idsig_path, 0, idsig_size, false).unwrap(),
+ |dev| loopdevice::detach(dev).unwrap(),
+ );
let name = "loop_as_input";
// Run the program WITH the loop devices, not the regular files.
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 5d36f16..64658a9 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,21 +16,28 @@
package com.android.virt.fs;
+import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import android.platform.test.annotations.RootPermissionTest;
import android.virt.test.CommandRunner;
import android.virt.test.VirtualizationTestCaseBase;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -47,7 +54,8 @@
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
-import java.util.Optional;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -62,6 +70,12 @@
/** Output directory where the test can generate output on Android */
private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
+ /** File name of the test APK */
+ private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
+
+ /** VM config entry path in the test APK */
+ private static final String VM_CONFIG_PATH_IN_APK = "assets/vm_config_extra_apk.json";
+
/** Path to open_then_run on Android */
private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
@@ -75,9 +89,7 @@
private static final String AUTHFS_BIN = "/system/bin/authfs";
/** Idsig paths to be created for each APK in the "extra_apks" of vm_config_extra_apk.json. */
- private static final String[] EXTRA_IDSIG_PATHS = new String[] {
- TEST_DIR + "BuildManifest.apk.idsig",
- };
+ private static final String EXTRA_IDSIG_PATH = TEST_DIR + "BuildManifest.apk.idsig";
/** Build manifest path in the VM. 0 is the index of extra_apks in vm_config_extra_apk.json. */
private static final String BUILD_MANIFEST_PATH = "/mnt/extra-apk/0/assets/build_manifest.pb";
@@ -99,7 +111,7 @@
private static final int VMADDR_CID_HOST = 2;
private static CommandRunner sAndroid;
- private static String sCid;
+ private static CommandRunner sMicrodroid;
private static boolean sAssumptionFailed;
private ExecutorService mThreadPool = Executors.newCachedThreadPool();
@@ -127,30 +139,20 @@
return;
}
- prepareVirtualizationTestSetup(androidDevice);
-
// For each test case, boot and adb connect to a new Microdroid
CLog.i("Starting the shared VM");
- final String apkName = "MicrodroidTestApp.apk";
- final String packageName = "com.android.microdroid.test";
- final String configPath = "assets/vm_config_extra_apk.json"; // path inside the APK
- sCid =
- startMicrodroid(
- androidDevice,
- testInfo.getBuildInfo(),
- apkName,
- packageName,
- EXTRA_IDSIG_PATHS,
- configPath,
- /* debug */ true,
- /* use default memoryMib */ 0,
- Optional.empty(),
- Optional.empty());
- adbConnectToMicrodroid(androidDevice, sCid);
+ ITestDevice microdroidDevice =
+ MicrodroidBuilder
+ .fromFile(findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
+ .debugLevel("full")
+ .addExtraIdsigPath(EXTRA_IDSIG_PATH)
+ .build((TestDevice) androidDevice);
// Root because authfs (started from shell in this test) currently require root to open
// /dev/fuse and mount the FUSE.
- rootMicrodroid();
+ assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+
+ sMicrodroid = new CommandRunner(microdroidDevice);
}
@AfterClassWithInfo
@@ -158,13 +160,12 @@
throws DeviceNotAvailableException {
assertNotNull(sAndroid);
- if (sCid != null) {
+ if (sMicrodroid != null) {
CLog.i("Shutting down shared VM");
- shutdownMicrodroid(sAndroid.getDevice(), sCid);
- sCid = null;
+ ((TestDevice) testInfo.getDevice()).shutdownMicrodroid(sMicrodroid.getDevice());
+ sMicrodroid = null;
}
- cleanUpVirtualizationTestSetup(sAndroid.getDevice());
sAndroid = null;
}
@@ -176,9 +177,13 @@
@After
public void tearDown() throws Exception {
+ if (sMicrodroid != null) {
+ sMicrodroid.tryRun("killall authfs");
+ sMicrodroid.tryRun("umount " + MOUNT_DIR);
+ }
+
+ assertNotNull(sAndroid);
sAndroid.tryRun("killall fd_server");
- tryRunOnMicrodroid("killall authfs");
- tryRunOnMicrodroid("umount " + MOUNT_DIR);
// Even though we only run one VM for the whole class, and could have collect the VM log
// after all tests are done, TestLogData doesn't seem to work at class level. Hence,
@@ -319,7 +324,7 @@
// Verify
// Force dropping the page cache, so that the next read can be validated.
- runOnMicrodroid("echo 1 > /proc/sys/vm/drop_caches");
+ sMicrodroid.run("echo 1 > /proc/sys/vm/drop_caches");
// A read will fail if the backing data has been tampered.
assertFalse(checkReadAtFileOffsetOnMicrodroid(
destPath, /* offset */ 0, /* number */ 4096));
@@ -419,8 +424,8 @@
// Action
// Can create nested directories and can create a file in one.
- runOnMicrodroid("mkdir " + authfsOutputDir + "/new_dir");
- runOnMicrodroid("mkdir -p " + authfsOutputDir + "/we/need/to/go/deeper");
+ sMicrodroid.run("mkdir " + authfsOutputDir + "/new_dir");
+ sMicrodroid.run("mkdir -p " + authfsOutputDir + "/we/need/to/go/deeper");
createFileWithOnesOnMicrodroid(authfsOutputDir + "/new_dir/file1", 10000);
createFileWithOnesOnMicrodroid(authfsOutputDir + "/we/need/file2", 10000);
@@ -452,7 +457,7 @@
runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
// Action & Verify
- runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/file");
+ sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/file");
assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/file"), 3);
// Can override a file and write normally.
createFileWithOnesOnMicrodroid(authfsOutputDir + "/file", 10000);
@@ -472,13 +477,13 @@
runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
- runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/file");
- runOnMicrodroid("test -f " + authfsOutputDir + "/file");
+ sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/file");
+ sMicrodroid.run("test -f " + authfsOutputDir + "/file");
sAndroid.run("test -f " + androidOutputDir + "/file");
// Action & Verify
- runOnMicrodroid("rm " + authfsOutputDir + "/file");
- runOnMicrodroid("test ! -f " + authfsOutputDir + "/file");
+ sMicrodroid.run("rm " + authfsOutputDir + "/file");
+ sMicrodroid.run("test ! -f " + authfsOutputDir + "/file");
sAndroid.run("test ! -f " + androidOutputDir + "/file");
}
@@ -491,20 +496,20 @@
runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
- runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir/dir2");
- runOnMicrodroid("echo -n foo > " + authfsOutputDir + "/dir/file");
+ sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir/dir2");
+ sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/dir/file");
sAndroid.run("test -d " + androidOutputDir + "/dir/dir2");
// Action & Verify
- runOnMicrodroid("rmdir " + authfsOutputDir + "/dir/dir2");
- runOnMicrodroid("test ! -d " + authfsOutputDir + "/dir/dir2");
+ sMicrodroid.run("rmdir " + authfsOutputDir + "/dir/dir2");
+ sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir/dir2");
sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
// Can only delete a directory if empty
assertFailedOnMicrodroid("rmdir " + authfsOutputDir + "/dir");
- runOnMicrodroid("test -d " + authfsOutputDir + "/dir"); // still there
- runOnMicrodroid("rm " + authfsOutputDir + "/dir/file");
- runOnMicrodroid("rmdir " + authfsOutputDir + "/dir");
- runOnMicrodroid("test ! -d " + authfsOutputDir + "/dir");
+ sMicrodroid.run("test -d " + authfsOutputDir + "/dir"); // still there
+ sMicrodroid.run("rm " + authfsOutputDir + "/dir/file");
+ sMicrodroid.run("rmdir " + authfsOutputDir + "/dir");
+ sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir");
sAndroid.run("test ! -d " + androidOutputDir + "/dir");
}
@@ -517,10 +522,10 @@
runFdServerOnAndroid("--open-dir 3:" + androidOutputDir, "--rw-dirs 3");
runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
- runOnMicrodroid("touch " + authfsOutputDir + "/some_file");
- runOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir");
- runOnMicrodroid("touch " + authfsOutputDir + "/some_dir/file");
- runOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/dir");
+ sMicrodroid.run("touch " + authfsOutputDir + "/some_file");
+ sMicrodroid.run("mkdir " + authfsOutputDir + "/some_dir");
+ sMicrodroid.run("touch " + authfsOutputDir + "/some_dir/file");
+ sMicrodroid.run("mkdir " + authfsOutputDir + "/some_dir/dir");
// Action & Verify
// Cannot create directory if an entry with the same name already exists.
@@ -542,12 +547,12 @@
// Create a file with some data. Test the existence.
String outputPath = authfsOutputDir + "/out";
String androidOutputPath = androidOutputDir + "/out";
- runOnMicrodroid("echo -n 123 > " + outputPath);
- runOnMicrodroid("test -f " + outputPath);
+ sMicrodroid.run("echo -n 123 > " + outputPath);
+ sMicrodroid.run("test -f " + outputPath);
sAndroid.run("test -f " + androidOutputPath);
// Action
- String output = runOnMicrodroid(
+ String output = sMicrodroid.run(
// Open the file for append and read
"exec 4>>" + outputPath + " 5<" + outputPath + "; "
// Delete the file from the directory
@@ -560,7 +565,7 @@
// Verify
// Output contains all written data, while the files are deleted.
assertEquals("123456", output);
- runOnMicrodroid("test ! -f " + outputPath);
+ sMicrodroid.run("test ! -f " + outputPath);
sAndroid.run("test ! -f " + androidOutputDir + "/out");
}
@@ -590,7 +595,7 @@
+ VMADDR_CID_HOST);
// Verify
- runOnMicrodroid("test -f " + authfsInputDir + "/system/framework/services.jar");
+ sMicrodroid.run("test -f " + authfsInputDir + "/system/framework/services.jar");
assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
}
@@ -602,14 +607,14 @@
// Action
String authfsOutputDir = MOUNT_DIR + "/3";
- runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir/dir2/dir3");
- runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file1");
- runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file2");
- runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file3");
- runOnMicrodroid("touch " + authfsOutputDir + "/file");
+ sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir/dir2/dir3");
+ sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file1");
+ sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file2");
+ sMicrodroid.run("touch " + authfsOutputDir + "/dir/dir2/dir3/file3");
+ sMicrodroid.run("touch " + authfsOutputDir + "/file");
// Verify
- String[] actual = runOnMicrodroid("cd " + authfsOutputDir + "; find |sort").split("\n");
+ String[] actual = sMicrodroid.run("cd " + authfsOutputDir + "; find |sort").split("\n");
String[] expected = new String[] {
".",
"./dir",
@@ -622,14 +627,14 @@
assertEquals(expected, actual);
// Add more entries.
- runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir2");
- runOnMicrodroid("touch " + authfsOutputDir + "/file2");
+ sMicrodroid.run("mkdir -p " + authfsOutputDir + "/dir2");
+ sMicrodroid.run("touch " + authfsOutputDir + "/file2");
// Check new entries. Also check that the types are correct.
- actual = runOnMicrodroid(
+ actual = sMicrodroid.run(
"cd " + authfsOutputDir + "; find -maxdepth 1 -type f |sort").split("\n");
expected = new String[] {"./file", "./file2"};
assertEquals(expected, actual);
- actual = runOnMicrodroid(
+ actual = sMicrodroid.run(
"cd " + authfsOutputDir + "; find -maxdepth 1 -type d |sort").split("\n");
expected = new String[] {".", "./dir", "./dir2"};
assertEquals(expected, actual);
@@ -643,7 +648,7 @@
// Action & Verify
// Change mode
- runOnMicrodroid("chmod 321 " + MOUNT_DIR + "/3");
+ sMicrodroid.run("chmod 321 " + MOUNT_DIR + "/3");
expectFileMode("--wx-w---x", MOUNT_DIR + "/3", TEST_OUTPUT_DIR + "/file");
// Can't set the disallowed bits
assertFailedOnMicrodroid("chmod +s " + MOUNT_DIR + "/3");
@@ -659,14 +664,14 @@
// Action & Verify
String authfsOutputDir = MOUNT_DIR + "/3";
// Create with umask
- runOnMicrodroid("umask 000; mkdir " + authfsOutputDir + "/dir");
- runOnMicrodroid("umask 022; mkdir " + authfsOutputDir + "/dir/dir2");
+ sMicrodroid.run("umask 000; mkdir " + authfsOutputDir + "/dir");
+ sMicrodroid.run("umask 022; mkdir " + authfsOutputDir + "/dir/dir2");
expectFileMode("drwxrwxrwx", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
expectFileMode("drwxr-xr-x", authfsOutputDir + "/dir/dir2", TEST_OUTPUT_DIR + "/dir/dir2");
// Change mode
- runOnMicrodroid("chmod -w " + authfsOutputDir + "/dir/dir2");
+ sMicrodroid.run("chmod -w " + authfsOutputDir + "/dir/dir2");
expectFileMode("dr-xr-xr-x", authfsOutputDir + "/dir/dir2", TEST_OUTPUT_DIR + "/dir/dir2");
- runOnMicrodroid("chmod 321 " + authfsOutputDir + "/dir");
+ sMicrodroid.run("chmod 321 " + authfsOutputDir + "/dir");
expectFileMode("d-wx-w---x", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
// Can't set the disallowed bits
assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/dir/dir2");
@@ -682,14 +687,14 @@
// Action & Verify
String authfsOutputDir = MOUNT_DIR + "/3";
// Create with umask
- runOnMicrodroid("umask 000; echo -n foo > " + authfsOutputDir + "/file");
- runOnMicrodroid("umask 022; echo -n foo > " + authfsOutputDir + "/file2");
+ sMicrodroid.run("umask 000; echo -n foo > " + authfsOutputDir + "/file");
+ sMicrodroid.run("umask 022; echo -n foo > " + authfsOutputDir + "/file2");
expectFileMode("-rw-rw-rw-", authfsOutputDir + "/file", TEST_OUTPUT_DIR + "/file");
expectFileMode("-rw-r--r--", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
// Change mode
- runOnMicrodroid("chmod -w " + authfsOutputDir + "/file");
+ sMicrodroid.run("chmod -w " + authfsOutputDir + "/file");
expectFileMode("-r--r--r--", authfsOutputDir + "/file", TEST_OUTPUT_DIR + "/file");
- runOnMicrodroid("chmod 321 " + authfsOutputDir + "/file2");
+ sMicrodroid.run("chmod 321 " + authfsOutputDir + "/file2");
expectFileMode("--wx-w---x", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
// Can't set the disallowed bits
assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/file");
@@ -705,7 +710,16 @@
// Verify
// Magic matches. Has only 2 inodes (root and "/3").
assertEquals(
- FUSE_SUPER_MAGIC_HEX + " 2", runOnMicrodroid("stat -f -c '%t %c' " + MOUNT_DIR));
+ FUSE_SUPER_MAGIC_HEX + " 2", sMicrodroid.run("stat -f -c '%t %c' " + MOUNT_DIR));
+ }
+
+ private static File findTestApk(IBuildInfo buildInfo) {
+ try {
+ return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
+ } catch (FileNotFoundException e) {
+ fail("Missing test file: " + TEST_APK_NAME);
+ return null;
+ }
}
private void expectBackingFileConsistency(
@@ -719,8 +733,8 @@
"Inconsistent file hash on the backend storage", hashOnAuthFs, hashOfBackingFile);
}
- private String computeFileHashOnMicrodroid(String path) {
- String result = runOnMicrodroid("sha256sum " + path);
+ private String computeFileHashOnMicrodroid(String path) throws DeviceNotAvailableException {
+ String result = sMicrodroid.run("sha256sum " + path);
String[] tokens = result.split("\\s");
if (tokens.length > 0) {
return tokens[0];
@@ -735,7 +749,7 @@
// TODO(b/182576497): cp returns error because close(2) returns ENOSYS in the current authfs
// implementation. We should probably fix that since programs can expect close(2) return 0.
String cmd = "cat " + src + " > " + dest;
- return tryRunOnMicrodroid(cmd) != null;
+ return sMicrodroid.tryRun(cmd) != null;
}
private String computeFileHashOnAndroid(String path) throws DeviceNotAvailableException {
@@ -751,38 +765,42 @@
private void expectFileMode(String expected, String microdroidPath, String androidPath)
throws DeviceNotAvailableException {
- String actual = runOnMicrodroid("stat -c '%A' " + microdroidPath);
+ String actual = sMicrodroid.run("stat -c '%A' " + microdroidPath);
assertEquals("Inconsistent mode for " + microdroidPath, expected, actual);
actual = sAndroid.run("stat -c '%A' " + androidPath);
assertEquals("Inconsistent mode for " + androidPath + " (android)", expected, actual);
}
- private boolean resizeFileOnMicrodroid(String path, long size) {
- CommandResult result = runOnMicrodroidForResult("truncate -c -s " + size + " " + path);
+ private boolean resizeFileOnMicrodroid(String path, long size)
+ throws DeviceNotAvailableException {
+ CommandResult result = sMicrodroid.runForResult("truncate -c -s " + size + " " + path);
return result.getStatus() == CommandStatus.SUCCESS;
}
- private long getFileSizeInBytesOnMicrodroid(String path) {
- return Long.parseLong(runOnMicrodroid("stat -c '%s' " + path));
+ private long getFileSizeInBytesOnMicrodroid(String path) throws DeviceNotAvailableException {
+ return Long.parseLong(sMicrodroid.run("stat -c '%s' " + path));
}
- private void createFileWithOnesOnMicrodroid(String filePath, long numberOfOnes) {
- runOnMicrodroid(
+ private void createFileWithOnesOnMicrodroid(String filePath, long numberOfOnes)
+ throws DeviceNotAvailableException {
+ sMicrodroid.run(
"yes $'\\x01' | tr -d '\\n' | dd bs=1 count=" + numberOfOnes + " of=" + filePath);
}
- private boolean checkReadAtFileOffsetOnMicrodroid(String filePath, long offset, long size) {
+ private boolean checkReadAtFileOffsetOnMicrodroid(String filePath, long offset, long size)
+ throws DeviceNotAvailableException {
String cmd = "dd if=" + filePath + " of=/dev/null bs=1 count=" + size;
if (offset > 0) {
cmd += " skip=" + offset;
}
- CommandResult result = runOnMicrodroidForResult(cmd);
+ CommandResult result = sMicrodroid.runForResult(cmd);
return result.getStatus() == CommandStatus.SUCCESS;
}
private boolean writeZerosAtFileOffsetOnMicrodroid(
- String filePath, long offset, long numberOfZeros, boolean writeThrough) {
+ String filePath, long offset, long numberOfZeros, boolean writeThrough)
+ throws DeviceNotAvailableException {
String cmd = "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros
+ " conv=notrunc";
if (offset > 0) {
@@ -791,7 +809,7 @@
if (writeThrough) {
cmd += " direct";
}
- CommandResult result = runOnMicrodroidForResult(cmd);
+ CommandResult result = sMicrodroid.runForResult(cmd);
return result.getStatus() == CommandStatus.SUCCESS;
}
@@ -810,9 +828,14 @@
// authfs may fail to start if fd_server is not yet listening on the vsock
// ("Error: Invalid raw AIBinder"). Just restart if that happens.
while (starting.get()) {
- CLog.i("Starting authfs");
- CommandResult result = runOnMicrodroidForResult(cmd);
- CLog.w("authfs has stopped: " + result);
+ try {
+ CLog.i("Starting authfs");
+ CommandResult result = sMicrodroid.runForResult(cmd);
+ CLog.w("authfs has stopped: " + result);
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Error running authfs", e);
+ throw new RuntimeException(e);
+ }
}
});
try {
@@ -855,8 +878,8 @@
});
}
- private boolean isMicrodroidDirectoryOnFuse(String path) {
- String fs_type = tryRunOnMicrodroid("stat -f -c '%t' " + path);
+ private boolean isMicrodroidDirectoryOnFuse(String path) throws DeviceNotAvailableException {
+ String fs_type = sMicrodroid.tryRun("stat -f -c '%t' " + path);
return FUSE_SUPER_MAGIC_HEX.equals(fs_type);
}
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 60f8fd4..b7d844f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -79,6 +79,7 @@
"tombstoned",
"tombstone_transmit.microdroid",
"cgroups.json",
+ "task_profiles.json",
"public.libraries.android.txt",
"microdroid_compatibility_matrix",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 8c85d3e..8a638db 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -146,17 +146,17 @@
}
}
-fn dice_derivation(verified_data: MicrodroidData, payload_config_path: &str) -> Result<()> {
+fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
// Calculate compound digests of code and authorities
let mut code_hash_ctx = digest::Context::new(&digest::SHA512);
let mut authority_hash_ctx = digest::Context::new(&digest::SHA512);
code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
- for extra_apk in verified_data.extra_apks_data {
+ for extra_apk in &verified_data.extra_apks_data {
code_hash_ctx.update(extra_apk.root_hash.as_ref());
authority_hash_ctx.update(extra_apk.pubkey.as_ref());
}
- for apex in verified_data.apex_data {
+ for apex in &verified_data.apex_data {
code_hash_ctx.update(apex.root_digest.as_ref());
authority_hash_ctx.update(apex.public_key.as_ref());
}
@@ -189,7 +189,7 @@
authorityHash: authority_hash,
authorityDescriptor: None,
mode: if app_debuggable { Mode::DEBUG } else { Mode::NORMAL },
- hidden: verified_data.salt.try_into().unwrap(),
+ hidden: verified_data.salt.clone().try_into().unwrap(),
}])
.context("IDiceMaintenance::demoteSelf failed")?;
Ok(())
@@ -240,6 +240,10 @@
instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
}
+ // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
+ info!("DICE derivation for payload");
+ dice_derivation(&verified_data, &metadata.payload_config_path)?;
+
// Before reading a file from the APK, start zipfuse
run_zipfuse(
"fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
@@ -270,9 +274,6 @@
}
mount_extra_apks(&config)?;
- info!("DICE derivation for payload");
- dice_derivation(verified_data, &metadata.payload_config_path)?;
-
// Wait until apex config is done. (e.g. linker configuration for apexes)
// TODO(jooyung): wait until sys.boot_completed?
wait_for_apex_config_done()?;
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index fb1373d..fbdd8d7 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -12,6 +12,9 @@
"libcompiler_builtins.rust_sysroot",
"libcore.rust_sysroot",
],
+ rustlibs: [
+ "libspin_nostd",
+ ],
enabled: false,
target: {
android_arm64: {
@@ -25,6 +28,7 @@
name: "pvmfw",
srcs: [
"entry.S",
+ "idmap.S",
],
static_libs: [
"libpvmfw",
diff --git a/pvmfw/entry.S b/pvmfw/entry.S
index 25631cb..e5c6045 100644
--- a/pvmfw/entry.S
+++ b/pvmfw/entry.S
@@ -19,6 +19,60 @@
add \reg, \reg, :lo12:\sym
.endm
+.macro mov_i, reg:req, imm:req
+ movz \reg, :abs_g3:\imm
+ movk \reg, :abs_g2_nc:\imm
+ movk \reg, :abs_g1_nc:\imm
+ movk \reg, :abs_g0_nc:\imm
+.endm
+
+.set .L_MAIR_DEV_nGnRE, 0x04
+.set .L_MAIR_MEM_WBWA, 0xff
+.set .Lmairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)
+
+/* 4 KiB granule size for TTBR0_EL1. */
+.set .L_TCR_TG0_4KB, 0x0 << 14
+/* 4 KiB granule size for TTBR1_EL1. */
+.set .L_TCR_TG1_4KB, 0x2 << 30
+/* Disable translation table walk for TTBR1_EL1, generating a translation fault instead. */
+.set .L_TCR_EPD1, 0x1 << 23
+/* Translation table walks for TTBR0_EL1 are inner sharable. */
+.set .L_TCR_SH_INNER, 0x3 << 12
+/*
+ * Translation table walks for TTBR0_EL1 are outer write-back read-allocate write-allocate
+ * cacheable.
+ */
+.set .L_TCR_RGN_OWB, 0x1 << 10
+/*
+ * Translation table walks for TTBR0_EL1 are inner write-back read-allocate write-allocate
+ * cacheable.
+ */
+.set .L_TCR_RGN_IWB, 0x1 << 8
+/* Size offset for TTBR0_EL1 is 2**39 bytes (512 GiB). */
+.set .L_TCR_T0SZ_512, 64 - 39
+.set .Ltcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_RGN_OWB
+.set .Ltcrval, .Ltcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | .L_TCR_T0SZ_512
+
+/* Stage 1 instruction access cacheability is unaffected. */
+.set .L_SCTLR_ELx_I, 0x1 << 12
+/* SP alignment fault if SP is not aligned to a 16 byte boundary. */
+.set .L_SCTLR_ELx_SA, 0x1 << 3
+/* Stage 1 data access cacheability is unaffected. */
+.set .L_SCTLR_ELx_C, 0x1 << 2
+/* EL0 and EL1 stage 1 MMU enabled. */
+.set .L_SCTLR_ELx_M, 0x1 << 0
+/* Privileged Access Never is unchanged on taking an exception to EL1. */
+.set .L_SCTLR_EL1_SPAN, 0x1 << 23
+/* All writable memory regions are treated as XN. */
+.set .L_SCTLR_EL1_WXN, 0x1 << 19
+/* SETEND instruction disabled at EL0 in aarch32 mode. */
+.set .L_SCTLR_EL1_SED, 0x1 << 8
+/* Various IT instructions are disabled at EL0 in aarch32 mode. */
+.set .L_SCTLR_EL1_ITD, 0x1 << 7
+.set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29)
+.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
+.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1 | .L_SCTLR_EL1_WXN
+
/**
* This is a generic entry point for an image. It carries out the operations
* required to prepare the loaded image to be run. Specifically, it zeroes the
@@ -28,6 +82,41 @@
.section .init.entry, "ax"
.global entry
entry:
+ /* Enable MMU and caches. */
+
+ /*
+ * Load and apply the memory management configuration.
+ */
+ adrp x1, idmap
+ mov_i x2, .Lmairval
+ mov_i x3, .Ltcrval
+ mov_i x4, .Lsctlrval
+
+ /* Copy the supported PA range into TCR_EL1.IPS. */
+ mrs x6, id_aa64mmfr0_el1
+ bfi x3, x6, #32, #4
+
+ msr ttbr0_el1, x1
+ msr mair_el1, x2
+ msr tcr_el1, x3
+
+ /*
+ * Ensure everything before this point has completed, then invalidate any potentially stale
+ * local TLB entries before they start being used.
+ */
+ isb
+ tlbi vmalle1
+ ic iallu
+ dsb nsh
+ isb
+
+ /*
+ * Configure sctlr_el1 to enable MMU and cache and don't proceed until
+ * this has completed.
+ */
+ msr sctlr_el1, x4
+ isb
+
/* Disable trapping floating point access in EL1. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
@@ -42,13 +131,23 @@
stp xzr, xzr, [x29], #16
b 0b
-1: /* Prepare the stack. */
- adr x30, boot_stack_end
+1: /* Copy the data section. */
+ adr_l x28, data_begin
+ adr_l x29, data_end
+ adr_l x30, data_lma
+2: cmp x28, x29
+ b.ge 3f
+ ldp q0, q1, [x30], #32
+ stp q0, q1, [x28], #32
+ b 2b
+
+3: /* Prepare the stack. */
+ adr_l x30, boot_stack_end
mov sp, x30
/* Call into Rust code. */
bl main
/* Loop forever waiting for interrupts. */
-2: wfi
- b 2b
+4: wfi
+ b 4b
diff --git a/pvmfw/idmap.S b/pvmfw/idmap.S
new file mode 100644
index 0000000..f1df6cc
--- /dev/null
+++ b/pvmfw/idmap.S
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.
+ */
+
+.set .L_TT_TYPE_BLOCK, 0x1
+.set .L_TT_TYPE_PAGE, 0x3
+.set .L_TT_TYPE_TABLE, 0x3
+
+/* Access flag. */
+.set .L_TT_AF, 0x1 << 10
+/* Not global. */
+.set .L_TT_NG, 0x1 << 11
+.set .L_TT_RO, 0x2 << 6
+.set .L_TT_XN, 0x3 << 53
+
+.set .L_TT_MT_DEV, 0x0 << 2 // MAIR #0 (DEV_nGnRE)
+.set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8) // MAIR #1 (MEM_WBWA), inner shareable
+
+.set .L_BLOCK_RO, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_RO | .L_TT_XN
+.set .L_BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN
+.set .L_BLOCK_MEM, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_XN | .L_TT_NG
+.set .L_BLOCK_MEM_XIP, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_NG | .L_TT_RO
+
+.section ".rodata.idmap", "a", %progbits
+.global idmap
+.align 12
+idmap:
+ /* level 1 */
+ .quad .L_BLOCK_DEV | 0x0 // 1 GB of device mappings
+ .quad .L_BLOCK_DEV | 0x40000000 // Another 1 GB of device mapppings
+ .quad .L_TT_TYPE_TABLE + 0f // up to 1 GB of DRAM
+ .fill 509, 8, 0x0 // 509 GB of remaining VA space
+
+ /* level 2 */
+0: .quad .L_BLOCK_RO | 0x80000000 // DT provided by VMM
+ .quad .L_BLOCK_MEM_XIP | 0x80200000 // 2 MB of DRAM containing image
+ .quad .L_BLOCK_MEM | 0x80400000 // 2 MB of writable DRAM
+ .fill 509, 8, 0x0
diff --git a/pvmfw/image.ld b/pvmfw/image.ld
index e08fbe2..4655f68 100644
--- a/pvmfw/image.ld
+++ b/pvmfw/image.ld
@@ -18,6 +18,7 @@
{
dtb_region : ORIGIN = 0x80000000, LENGTH = 2M
image : ORIGIN = 0x80200000, LENGTH = 2M
+ writable_data : ORIGIN = 0x80400000, LENGTH = 2M
}
/*
@@ -82,7 +83,9 @@
*/
. = ALIGN(32);
data_end = .;
- } >image
+ } >writable_data AT>image
+ data_lma = LOADADDR(.data);
+
/* Everything beyond this point will not be included in the binary. */
bin_end = .;
@@ -93,14 +96,14 @@
*(COMMON)
. = ALIGN(16);
bss_end = .;
- } >image
+ } >writable_data
.stack (NOLOAD) : ALIGN(4096) {
boot_stack_begin = .;
. += 40 * 4096;
. = ALIGN(4096);
boot_stack_end = .;
- } >image
+ } >writable_data
/*
* Remove unused sections from the image.
diff --git a/pvmfw/src/console.rs b/pvmfw/src/console.rs
new file mode 100644
index 0000000..b52d924
--- /dev/null
+++ b/pvmfw/src/console.rs
@@ -0,0 +1,112 @@
+// Copyright 2022, 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.
+
+//! Console driver for 8250 UART.
+
+use crate::uart::Uart;
+use core::fmt::{write, Arguments, Write};
+use spin::mutex::SpinMutex;
+
+const BASE_ADDRESS: usize = 0x3f8;
+
+static CONSOLE: SpinMutex<Option<Uart>> = SpinMutex::new(None);
+
+/// Initialises a new instance of the UART driver and returns it.
+fn create() -> Uart {
+ // Safe because BASE_ADDRESS is the base of the MMIO region for a UART and is mapped as device
+ // memory.
+ unsafe { Uart::new(BASE_ADDRESS) }
+}
+
+/// Initialises the global instance of the UART driver. This must be called before using
+/// the `print!` and `println!` macros.
+pub fn init() {
+ let uart = create();
+ CONSOLE.lock().replace(uart);
+}
+
+/// Writes a string to the console.
+///
+/// Panics if [`init`] was not called first.
+pub fn write_str(s: &str) {
+ CONSOLE.lock().as_mut().unwrap().write_str(s).unwrap();
+}
+
+/// Writes a formatted string to the console.
+///
+/// Panics if [`init`] was not called first.
+pub fn write_args(format_args: Arguments) {
+ write(CONSOLE.lock().as_mut().unwrap(), format_args).unwrap();
+}
+
+/// Reinitialises the UART driver and writes a string to it.
+///
+/// This is intended for use in situations where the UART may be in an unknown state or the global
+/// instance may be locked, such as in an exception handler or panic handler.
+pub fn emergency_write_str(s: &str) {
+ let mut uart = create();
+ let _ = uart.write_str(s);
+}
+
+/// Reinitialises the UART driver and writes a formatted string to it.
+///
+/// This is intended for use in situations where the UART may be in an unknown state or the global
+/// instance may be locked, such as in an exception handler or panic handler.
+pub fn emergency_write_args(format_args: Arguments) {
+ let mut uart = create();
+ let _ = write(&mut uart, format_args);
+}
+
+/// Prints the given string to the console.
+///
+/// Panics if the console has not yet been initialised. May hang if used in an exception context;
+/// use `eprint!` instead.
+#[macro_export]
+macro_rules! print {
+ ($($arg:tt)*) => ($crate::console::write_args(format_args!($($arg)*)));
+}
+
+/// Prints the given formatted string to the console, followed by a newline.
+///
+/// Panics if the console has not yet been initialised. May hang if used in an exception context;
+/// use `eprintln!` instead.
+#[macro_export]
+macro_rules! println {
+ () => ($crate::console::write_str("\n"));
+ ($($arg:tt)*) => ({
+ $crate::console::write_args(format_args!($($arg)*))};
+ $crate::console::write_str("\n");
+ );
+}
+
+/// Prints the given string to the console in an emergency, such as an exception handler.
+///
+/// Never panics.
+#[macro_export]
+macro_rules! eprint {
+ ($($arg:tt)*) => ($crate::console::emergency_write_args(format_args!($($arg)*)));
+}
+
+/// Prints the given string followed by a newline to the console in an emergency, such as an
+/// exception handler.
+///
+/// Never panics.
+#[macro_export]
+macro_rules! eprintln {
+ () => ($crate::console::emergency_write_str("\n"));
+ ($($arg:tt)*) => ({
+ $crate::console::emergency_write_args(format_args!($($arg)*))};
+ $crate::console::emergency_write_str("\n");
+ );
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 0a359f6..4ab14b7 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -17,7 +17,9 @@
#![no_main]
#![no_std]
+mod console;
mod psci;
+mod uart;
use core::panic::PanicInfo;
use psci::{system_off, system_reset};
@@ -25,13 +27,17 @@
/// Entry point for pVM firmware.
#[no_mangle]
pub extern "C" fn main() -> ! {
+ console::init();
+ println!("Hello world");
+
system_off();
#[allow(clippy::empty_loop)]
loop {}
}
#[panic_handler]
-fn panic(_info: &PanicInfo) -> ! {
+fn panic(info: &PanicInfo) -> ! {
+ eprintln!("{}", info);
system_reset();
loop {}
}
diff --git a/pvmfw/src/uart.rs b/pvmfw/src/uart.rs
new file mode 100644
index 0000000..0fc2494
--- /dev/null
+++ b/pvmfw/src/uart.rs
@@ -0,0 +1,59 @@
+// Copyright 2022, 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.
+
+//! Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
+//! provided by crosvm, and won't work with real hardware.
+
+use core::fmt::{self, Write};
+use core::ptr::write_volatile;
+
+/// Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
+/// provided by crosvm, and won't work with real hardware.
+pub struct Uart {
+ base_address: *mut u8,
+}
+
+impl Uart {
+ /// Constructs a new instance of the UART driver for a device at the given base address.
+ ///
+ /// # Safety
+ ///
+ /// The given base address must point to the 8 MMIO control registers of an appropriate UART
+ /// device, which must be mapped into the address space of the process as device memory and not
+ /// have any other aliases.
+ pub unsafe fn new(base_address: usize) -> Self {
+ Self { base_address: base_address as *mut u8 }
+ }
+
+ /// Writes a single byte to the UART.
+ pub fn write_byte(&self, byte: u8) {
+ // Safe because we know that the base address points to the control registers of an UART
+ // device which is appropriately mapped.
+ unsafe {
+ write_volatile(self.base_address, byte);
+ }
+ }
+}
+
+impl Write for Uart {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ for c in s.as_bytes() {
+ self.write_byte(*c);
+ }
+ Ok(())
+ }
+}
+
+// Safe because it just contains a pointer to device memory, which can be accessed from any context.
+unsafe impl Send for Uart {}
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 7944245..32bdf3b 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -153,7 +153,9 @@
command.add(virtApexDir.getPath());
CommandResult result = runUtil.runTimedCmd(
- 20 * 1000,
+ // sign_virt_apex is so slow on CI server that this often times
+ // out. Until we can make it fast, use 50s for timeout
+ 50 * 1000,
"/bin/bash",
"-c",
String.join(" ", command));