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
This commit is contained in:
parent
e275568b0d
commit
197748b652
|
@ -18,7 +18,6 @@ rust_defaults {
|
|||
"liblibc",
|
||||
"libnix",
|
||||
"libnum_traits",
|
||||
"librustutils",
|
||||
"libscopeguard",
|
||||
"libuuid",
|
||||
],
|
||||
|
|
|
@ -29,7 +29,6 @@ use anyhow::{bail, Context, Result};
|
|||
use clap::{App, Arg};
|
||||
use idsig::{HashAlgorithm, V4Signature};
|
||||
use itertools::Itertools;
|
||||
use rustutils::system_properties;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
|
@ -40,27 +39,26 @@ fn main() -> Result<()> {
|
|||
let matches = App::new("apkdmverity")
|
||||
.about("Creates a dm-verity block device out of APK signed with APK signature scheme V4.")
|
||||
.arg(Arg::from_usage(
|
||||
"--apk... <apk_path> <idsig_path> <name> \
|
||||
'Input APK file, idsig file, and the name of the block device. The APK \
|
||||
file must be signed using the APK signature scheme 4. The block device \
|
||||
is created at \"/dev/mapper/<name>\".'",
|
||||
"--apk... <apk_path> <idsig_path> <name> <root_hash> \
|
||||
'Input APK file, idsig file, name of the block device, and root hash. \
|
||||
The APK file must be signed using the APK signature scheme 4. The \
|
||||
block device is created at \"/dev/mapper/<name>\".' root_hash is \
|
||||
optional; idsig file's root hash will be used if specified as \"none\"."
|
||||
))
|
||||
.arg(Arg::with_name("verbose").short("v").long("verbose").help("Shows verbose output"))
|
||||
.get_matches();
|
||||
|
||||
let apks = matches.values_of("apk").unwrap();
|
||||
assert!(apks.len() % 3 == 0);
|
||||
|
||||
let roothash = if let Ok(val) = system_properties::read("microdroid_manager.apk_root_hash") {
|
||||
Some(util::parse_hexstring(&val)?)
|
||||
} else {
|
||||
// This failure is not an error. We will use the roothash read from the idsig file.
|
||||
None
|
||||
};
|
||||
assert!(apks.len() % 4 == 0);
|
||||
|
||||
let verbose = matches.is_present("verbose");
|
||||
|
||||
for (apk, idsig, name) in apks.tuples() {
|
||||
for (apk, idsig, name, roothash) in apks.tuples() {
|
||||
let roothash = if roothash != "none" {
|
||||
Some(util::parse_hexstring(roothash).expect("failed to parse roothash"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ret = enable_verity(apk, idsig, name, roothash.as_deref())?;
|
||||
if verbose {
|
||||
println!(
|
||||
|
|
|
@ -18,9 +18,13 @@ on early-init
|
|||
start ueventd
|
||||
|
||||
mkdir /mnt/apk 0755 system system
|
||||
mkdir /mnt/extra-apk 0755 root root
|
||||
# Microdroid_manager starts apkdmverity/zipfuse/apexd
|
||||
start microdroid_manager
|
||||
|
||||
# restorecon so microdroid_manager can create subdirectories
|
||||
restorecon /mnt/extra-apk
|
||||
|
||||
# Wait for apexd to finish activating APEXes before starting more processes.
|
||||
wait_for_prop apexd.status activated
|
||||
perform_apex_config
|
||||
|
|
|
@ -17,7 +17,9 @@ rust_defaults {
|
|||
"libbinder_rpc_unstable_bindgen",
|
||||
"libbinder_rs",
|
||||
"libbyteorder",
|
||||
"libglob",
|
||||
"libidsig",
|
||||
"libitertools",
|
||||
"libkernlog",
|
||||
"liblibc",
|
||||
"liblog_rust",
|
||||
|
|
|
@ -314,6 +314,7 @@ fn get_key() -> ZeroOnDropKey {
|
|||
#[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]>,
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ use anyhow::{anyhow, bail, ensure, Context, Error, Result};
|
|||
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 once_cell::sync::OnceCell;
|
|||
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 @@ use android_system_virtualmachineservice::aidl::android::system::virtualmachines
|
|||
};
|
||||
|
||||
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 @@ fn try_run_payload(service: &Strong<dyn IVirtualMachineService>) -> Result<i32>
|
|||
!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 @@ struct ApkDmverityArgument<'a> {
|
|||
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 @@ fn run_apkdmverity(args: &[ApkDmverityArgument]) -> Result<Child> {
|
|||
|
||||
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 @@ fn verify_payload(
|
|||
);
|
||||
}
|
||||
|
||||
// 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 @@ fn verify_payload(
|
|||
// 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 @@ fn wait_for_apex_config_done() -> Result<()> {
|
|||
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> {
|
||||
|
|
Loading…
Reference in New Issue