Verify and mount extra apks
virtualizationservice currently passes extra apks and corresponding
idsigs as block devices. microdroid_manager will read the payload config
and mount the apks to /mnt/extra-apk/{index} after verifying them.
Bug: 205224817
Test: atest ComposHostTestCases MicrodroidHostTestCases
Test: manually edit vm config and see apks mounted
Change-Id: I9afa03cb7fabe0ca16b7926c4480d0b32c8bcd04
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 8ba6f51..aadb71f 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -314,6 +314,7 @@
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct MicrodroidData {
pub apk_data: ApkData,
+ pub extra_apks_data: Vec<ApkData>,
pub apex_data: Vec<ApexData>,
pub bootconfig: Box<[u8]>,
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index efe6126..fccf031 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -23,7 +23,9 @@
use apkverify::{get_public_key_der, verify};
use binder::unstable_api::{new_spibinder, AIBinder};
use binder::{FromIBinder, Strong};
+use glob::glob;
use idsig::V4Signature;
+use itertools::sorted;
use log::{error, info, warn};
use microdroid_metadata::{write_metadata, Metadata};
use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
@@ -31,7 +33,7 @@
use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
-use std::fs::{self, File, OpenOptions};
+use std::fs::{self, create_dir, File, OpenOptions};
use std::os::unix::io::{FromRawFd, IntoRawFd};
use std::path::Path;
use std::process::{Child, Command, Stdio};
@@ -44,13 +46,11 @@
};
const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const APK_DM_VERITY_ARGUMENT: ApkDmverityArgument = {
- ApkDmverityArgument {
- apk: "/dev/block/by-name/microdroid-apk",
- idsig: "/dev/block/by-name/microdroid-apk-idsig",
- name: "microdroid-apk",
- }
-};
+const MAIN_APK_PATH: &str = "/dev/block/by-name/microdroid-apk";
+const MAIN_APK_IDSIG_PATH: &str = "/dev/block/by-name/microdroid-apk-idsig";
+const MAIN_APK_DEVICE_NAME: &str = "microdroid-apk";
+const EXTRA_APK_PATH_PATTERN: &str = "/dev/block/by-name/extra-apk-*";
+const EXTRA_IDSIG_PATH_PATTERN: &str = "/dev/block/by-name/extra-idsig-*";
const DM_MOUNTED_APK_PATH: &str = "/dev/block/mapper/microdroid-apk";
const APKDMVERITY_BIN: &str = "/system/bin/apkdmverity";
const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
@@ -170,7 +170,16 @@
!metadata.payload_config_path.is_empty(),
MicrodroidError::InvalidConfig("No payload_config_path in metadata".to_string())
);
+
let config = load_config(Path::new(&metadata.payload_config_path))?;
+ if config.extra_apks.len() != verified_data.extra_apks_data.len() {
+ return Err(anyhow!(
+ "config expects {} extra apks, but found only {}",
+ config.extra_apks.len(),
+ verified_data.extra_apks_data.len()
+ ));
+ }
+ mount_extra_apks(&config)?;
let fake_secret = "This is a placeholder for a value that is derived from the images that are loaded in the VM.";
if let Err(err) = rustutils::system_properties::write("ro.vmsecret.keymint", fake_secret) {
@@ -192,6 +201,7 @@
apk: &'a str,
idsig: &'a str,
name: &'a str,
+ saved_root_hash: Option<&'a RootHash>,
}
fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
@@ -201,6 +211,11 @@
for argument in args {
cmd.arg("--apk").arg(argument.apk).arg(argument.idsig).arg(argument.name);
+ if let Some(root_hash) = argument.saved_root_hash {
+ cmd.arg(&to_hex_string(root_hash));
+ } else {
+ cmd.arg("none");
+ }
}
cmd.spawn().context("Spawn apkdmverity")
@@ -236,19 +251,86 @@
);
}
+ // Verify main APK
let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
- let root_hash_from_idsig = get_apk_root_hash_from_idsig()?;
+ let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
// If root_hash can be trusted, pass it to apkdmverity so that it uses the passed root_hash
// instead of the value read from the idsig file.
- if root_hash_trustful {
- let root_hash = to_hex_string(root_hash.unwrap());
- system_properties::write("microdroid_manager.apk_root_hash", &root_hash)?;
+ let main_apk_argument = {
+ ApkDmverityArgument {
+ apk: MAIN_APK_PATH,
+ idsig: MAIN_APK_IDSIG_PATH,
+ name: MAIN_APK_DEVICE_NAME,
+ saved_root_hash: if root_hash_trustful {
+ Some(root_hash_from_idsig.as_ref())
+ } else {
+ None
+ },
+ }
+ };
+ let mut apkdmverity_arguments = vec![main_apk_argument];
+
+ // Verify extra APKs
+ // For now, we can't read the payload config, so glob APKs and idsigs.
+ // Later, we'll see if it matches with the payload config.
+
+ // sort globbed paths to match apks (extra-apk-{idx}) and idsigs (extra-idsig-{idx})
+ // e.g. "extra-apk-0" corresponds to "extra-idsig-0"
+ let extra_apks =
+ sorted(glob(EXTRA_APK_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+ let extra_idsigs =
+ sorted(glob(EXTRA_IDSIG_PATH_PATTERN)?.collect::<Result<Vec<_>, _>>()?).collect::<Vec<_>>();
+ if extra_apks.len() != extra_idsigs.len() {
+ return Err(anyhow!(
+ "Extra apks/idsigs mismatch: {} apks but {} idsigs",
+ extra_apks.len(),
+ extra_idsigs.len()
+ ));
+ }
+ let extra_apks_count = extra_apks.len();
+
+ let (extra_apk_names, extra_root_hashes_from_idsig): (Vec<_>, Vec<_>) = extra_idsigs
+ .iter()
+ .enumerate()
+ .map(|(i, extra_idsig)| {
+ (
+ format!("extra-apk-{}", i),
+ get_apk_root_hash_from_idsig(extra_idsig.to_str().unwrap())
+ .expect("Can't find root hash from extra idsig"),
+ )
+ })
+ .unzip();
+
+ let saved_extra_root_hashes: Vec<_> = saved_data
+ .map(|d| d.extra_apks_data.iter().map(|apk_data| &apk_data.root_hash).collect())
+ .unwrap_or_else(Vec::new);
+ let extra_root_hashes_trustful: Vec<_> = extra_root_hashes_from_idsig
+ .iter()
+ .enumerate()
+ .map(|(i, root_hash_from_idsig)| {
+ saved_extra_root_hashes.get(i).copied() == Some(root_hash_from_idsig)
+ })
+ .collect();
+
+ for i in 0..extra_apks_count {
+ apkdmverity_arguments.push({
+ ApkDmverityArgument {
+ apk: extra_apks[i].to_str().unwrap(),
+ idsig: extra_idsigs[i].to_str().unwrap(),
+ name: &extra_apk_names[i],
+ saved_root_hash: if extra_root_hashes_trustful[i] {
+ Some(&extra_root_hashes_from_idsig[i])
+ } else {
+ None
+ },
+ }
+ });
}
// Start apkdmverity and wait for the dm-verify block
- let mut apkdmverity_child = run_apkdmverity(&[APK_DM_VERITY_ARGUMENT])?;
+ let mut apkdmverity_child = run_apkdmverity(&apkdmverity_arguments)?;
// While waiting for apkdmverity to mount APK, gathers public keys and root digests from
// APEX payload.
@@ -280,26 +362,47 @@
// taken only when the root_hash is un-trustful which can be either when this is the first boot
// of the VM or APK was updated in the host.
// TODO(jooyung): consider multithreading to make this faster
- let apk_pubkey = if !root_hash_trustful {
- verify(DM_MOUNTED_APK_PATH).context(MicrodroidError::PayloadVerificationFailed(format!(
- "failed to verify {}",
- DM_MOUNTED_APK_PATH
- )))?
- } else {
- get_public_key_der(DM_MOUNTED_APK_PATH)?
- };
+ let main_apk_pubkey = get_public_key_from_apk(DM_MOUNTED_APK_PATH, root_hash_trustful)?;
+ let extra_apks_data = extra_root_hashes_from_idsig
+ .into_iter()
+ .enumerate()
+ .map(|(i, extra_root_hash)| {
+ let mount_path = format!("/dev/block/mapper/{}", &extra_apk_names[i]);
+ let apk_pubkey = get_public_key_from_apk(&mount_path, extra_root_hashes_trustful[i])?;
+ Ok(ApkData { root_hash: extra_root_hash, pubkey: apk_pubkey })
+ })
+ .collect::<Result<Vec<_>>>()?;
info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
// At this point, we can ensure that the root_hash from the idsig file is trusted, either by
// fully verifying the APK or by comparing it with the saved root_hash.
Ok(MicrodroidData {
- apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: apk_pubkey },
+ apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
+ extra_apks_data,
apex_data: apex_data_from_payload,
bootconfig: get_bootconfig()?.clone().into_boxed_slice(),
})
}
+fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+ // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
+ for i in 0..config.extra_apks.len() {
+ let mount_dir = format!("/mnt/extra-apk/{}", i);
+ create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
+
+ // don't wait, just detach
+ run_zipfuse(
+ "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
+ Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+ Path::new(&mount_dir),
+ )
+ .context("Failed to zipfuse extra apks")?;
+ }
+
+ Ok(())
+}
+
// Waits until linker config is generated
fn wait_for_apex_config_done() -> Result<()> {
let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
@@ -313,17 +416,26 @@
Ok(())
}
-fn get_apk_root_hash_from_idsig() -> Result<Box<RootHash>> {
- let mut idsig = File::open("/dev/block/by-name/microdroid-apk-idsig")?;
+fn get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>> {
+ let mut idsig = File::open(path)?;
let idsig = V4Signature::from(&mut idsig)?;
Ok(idsig.hashing_info.raw_root_hash)
}
+fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
+ if !root_hash_trustful {
+ verify(apk).context(MicrodroidError::PayloadVerificationFailed(format!(
+ "failed to verify {}",
+ apk
+ )))
+ } else {
+ get_public_key_der(apk)
+ }
+}
+
fn get_bootconfig() -> Result<&'static Vec<u8>> {
static VAL: OnceCell<Vec<u8>> = OnceCell::new();
- VAL.get_or_try_init(|| {
- fs::read("/proc/bootconfig").context("Failed to read bootconfig")
- })
+ VAL.get_or_try_init(|| fs::read("/proc/bootconfig").context("Failed to read bootconfig"))
}
fn load_config(path: &Path) -> Result<VmPayloadConfig> {