Merge "APK roothash is trusted"
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index df46324..e6b1ca9 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -17,6 +17,7 @@
         "liblibc",
         "libnix",
         "libnum_traits",
+        "librustutils",
         "libscopeguard",
         "libuuid",
     ],
@@ -31,6 +32,8 @@
 rust_binary {
     name: "apkdmverity",
     defaults: ["apkdmverity.defaults"],
+    init_rc: ["apkdmverity.rc"],
+    bootstrap: true,
 }
 
 rust_test {
diff --git a/apkdmverity/apkdmverity.rc b/apkdmverity/apkdmverity.rc
new file mode 100644
index 0000000..c6ef52b
--- /dev/null
+++ b/apkdmverity/apkdmverity.rc
@@ -0,0 +1,3 @@
+service apkdmverity /system/bin/apkdmverity /dev/block/by-name/microdroid-apk /dev/block/by-name/microdroid-apk-idsig microdroid-apk
+    disabled
+    oneshot
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index 9d1ef1c..2cc0758 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -28,6 +28,7 @@
 use anyhow::{bail, Context, Result};
 use clap::{App, Arg};
 use idsig::{HashAlgorithm, V4Signature};
+use rustutils::system_properties;
 use std::fmt::Debug;
 use std::fs;
 use std::fs::File;
@@ -61,7 +62,13 @@
     let apk = matches.value_of("apk").unwrap();
     let idsig = matches.value_of("idsig").unwrap();
     let name = matches.value_of("name").unwrap();
-    let ret = enable_verity(apk, idsig, name)?;
+    let roothash = if let Ok(val) = system_properties::read("microdroid_manager.apk_roothash") {
+        Some(util::parse_hexstring(&val)?)
+    } else {
+        // This failure is not an error. We will use the roothash read from the idsig file.
+        None
+    };
+    let ret = enable_verity(apk, idsig, name, roothash.as_deref())?;
     if matches.is_present("verbose") {
         println!(
             "data_device: {:?}, hash_device: {:?}, mapper_device: {:?}",
@@ -80,7 +87,12 @@
 const BLOCK_SIZE: u64 = 4096;
 
 // Makes a dm-verity block device out of `apk` and its accompanying `idsig` files.
-fn enable_verity<P: AsRef<Path> + Debug>(apk: P, idsig: P, name: &str) -> Result<VerityResult> {
+fn enable_verity<P: AsRef<Path> + Debug>(
+    apk: P,
+    idsig: P,
+    name: &str,
+    roothash: Option<&[u8]>,
+) -> Result<VerityResult> {
     // Attach the apk file to a loop device if the apk file is a regular file. If not (i.e. block
     // device), we only need to get the size and use the block device as it is.
     let (data_device, apk_size) = if fs::metadata(&apk)?.file_type().is_block_device() {
@@ -108,7 +120,11 @@
     let target = dm::DmVerityTargetBuilder::default()
         .data_device(&data_device, apk_size)
         .hash_device(&hash_device)
-        .root_digest(&sig.hashing_info.raw_root_hash)
+        .root_digest(if let Some(roothash) = roothash {
+            roothash
+        } else {
+            &sig.hashing_info.raw_root_hash
+        })
         .hash_algorithm(match sig.hashing_info.hash_algorithm {
             HashAlgorithm::SHA256 => dm::DmVerityHashAlgorithm::SHA256,
         })
@@ -167,6 +183,16 @@
     }
 
     fn run_test(apk: &[u8], idsig: &[u8], name: &str, check: fn(TestContext)) {
+        run_test_with_hash(apk, idsig, name, None, check);
+    }
+
+    fn run_test_with_hash(
+        apk: &[u8],
+        idsig: &[u8],
+        name: &str,
+        roothash: Option<&[u8]>,
+        check: fn(TestContext),
+    ) {
         if should_skip() {
             return;
         }
@@ -174,7 +200,7 @@
         let (apk_path, idsig_path) = prepare_inputs(test_dir.path(), apk, idsig);
 
         // Run the program and register clean-ups.
-        let ret = enable_verity(&apk_path, &idsig_path, name).unwrap();
+        let ret = enable_verity(&apk_path, &idsig_path, name, roothash).unwrap();
         let ret = scopeguard::guard(ret, |ret| {
             loopdevice::detach(ret.data_device).unwrap();
             loopdevice::detach(ret.hash_device).unwrap();
@@ -312,7 +338,8 @@
 
         let name = "loop_as_input";
         // Run the program WITH the loop devices, not the regular files.
-        let ret = enable_verity(apk_loop_device.deref(), idsig_loop_device.deref(), name).unwrap();
+        let ret =
+            enable_verity(apk_loop_device.deref(), idsig_loop_device.deref(), name, None).unwrap();
         let ret = scopeguard::guard(ret, |ret| {
             loopdevice::detach(ret.data_device).unwrap();
             loopdevice::detach(ret.hash_device).unwrap();
@@ -325,4 +352,18 @@
         assert_eq!(verity.len(), original.len()); // fail fast
         assert_eq!(verity.as_slice(), original.as_slice());
     }
+
+    // test with custom roothash
+    #[test]
+    fn correct_custom_roothash() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+        let roothash = V4Signature::from(Cursor::new(&idsig)).unwrap().hashing_info.raw_root_hash;
+        run_test_with_hash(apk.as_ref(), idsig.as_ref(), "correct", Some(&roothash), |ctx| {
+            let verity = fs::read(&ctx.result.mapper_device).unwrap();
+            let original = fs::read(&ctx.result.data_device).unwrap();
+            assert_eq!(verity.len(), original.len()); // fail fast
+            assert_eq!(verity.as_slice(), original.as_slice());
+        });
+    }
 }
diff --git a/apkdmverity/src/util.rs b/apkdmverity/src/util.rs
index d2bc799..913f827 100644
--- a/apkdmverity/src/util.rs
+++ b/apkdmverity/src/util.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Result};
 use nix::sys::stat::FileStat;
 use std::fs::File;
 use std::os::unix::fs::FileTypeExt;
@@ -42,6 +42,19 @@
     s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or_default()
 }
 
+/// Parses a hexadecimal string into a byte array
+pub fn parse_hexstring(s: &str) -> Result<Vec<u8>> {
+    let len = s.len();
+    if len % 2 != 0 {
+        bail!("length {} is not even", len)
+    } else {
+        (0..len)
+            .step_by(2)
+            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| anyhow!(e)))
+            .collect()
+    }
+}
+
 /// fstat that accepts a path rather than FD
 pub fn fstat(p: &Path) -> Result<FileStat> {
     let f = File::open(p)?;
diff --git a/microdroid/init.rc b/microdroid/init.rc
index f9cd915..f6a7ecc 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,7 @@
 
     start ueventd
 
+    mkdir /mnt/apk 0755 system system
     start microdroid_manager
     # TODO(b/190343842) verify apexes/apk before mounting them.
 
@@ -26,9 +27,10 @@
 
     perform_apex_config
 
-    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
+    # Notify to microdroid_manager that perform_apex_config is done.
+    # Microdroid_manager shouldn't execute payload before this, because app
+    # payloads are not designed to run with bootstrap bionic
+    setprop apex_config.done true
 
 on init
     # Mount binderfs
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 62af791..9cbcaf1 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -24,16 +24,17 @@
 use binder::unstable_api::{new_spibinder, AIBinder};
 use binder::{FromIBinder, Strong};
 use idsig::V4Signature;
-use log::{debug, error, info, warn};
+use log::{error, info, warn};
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
 use nix::ioctl_read_bad;
+use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
 use std::fs::{self, File, OpenOptions};
 use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
 use std::path::Path;
 use std::process::{Command, Stdio};
 use std::str;
-use std::time::Duration;
+use std::time::{Duration, SystemTime};
 use vsock::VsockStream;
 
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::{
@@ -46,6 +47,8 @@
 /// The CID representing the host VM
 const VMADDR_CID_HOST: u32 = 2;
 
+const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
+
 fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
     // safely taken by new_spibinder.
@@ -89,26 +92,36 @@
 
     let metadata = metadata::load()?;
 
-    if let Err(err) = verify_payloads() {
-        error!("failed to verify payload: {:#?}", err);
-        return Err(err);
+    // Try to read roothash from the instance disk.
+    // TODO(jiyong): the data should have an internal structure.
+    let mut instance = InstanceDisk::new()?;
+    let saved_roothash = instance.read_microdroid_data().context("Failed to read identity data")?;
+    let saved_roothash = saved_roothash.as_deref();
+
+    // Verify the payload before using it.
+    let verified_roothash =
+        verify_payload(saved_roothash).context("Payload verification failed")?;
+    if let Some(saved_roothash) = saved_roothash {
+        if saved_roothash == verified_roothash.as_ref() {
+            info!("Saved roothash is verified.");
+        } else {
+            bail!("Detected an update of the APK which isn't supported yet.");
+        }
+    } else {
+        info!("Saving APK roothash: {}", to_hex_string(verified_roothash.as_ref()));
+        // TODO(jiyong): the data should have an internal structure.
+        instance
+            .write_microdroid_data(verified_roothash.as_ref())
+            .context("Failed to write identity data")?;
     }
 
-    let mut instance = InstanceDisk::new()?;
-    // TODO(jiyong): the data should have an internal structure
-    if let Some(data) = instance.read_microdroid_data().context("Failed to read identity data")? {
-        debug!("read apk root hash: {}", to_hex_string(&data));
-        //TODO(jiyong) apkdmverity should use this root hash instead of the one read from the idsig
-        //file, if the root hash is found in the instance image.
-    } else {
-        let data = get_apk_roothash()?;
-        debug!("write apk root hash: {}", to_hex_string(&data));
-        instance.write_microdroid_data(data.as_ref()).context("Failed to write identity data")?;
-    }
+    wait_for_apex_config_done()?;
 
     let service = get_vms_rpc_binder().expect("cannot connect to VirtualMachineService");
-
     if !metadata.payload_config_path.is_empty() {
+        // Before reading a file from the APK, start zipfuse
+        system_properties::write("ctl.start", "zipfuse")?;
+
         let config = load_config(Path::new(&metadata.payload_config_path))?;
 
         let fake_secret = "This is a placeholder for a value that is derived from the images that are loaded in the VM.";
@@ -128,20 +141,58 @@
     Ok(())
 }
 
-// TODO(jooyung): v2/v3 full verification can be slow. Consider multithreading.
-fn verify_payloads() -> Result<()> {
-    // We don't verify APEXes since apexd does.
+type Roothash = [u8];
 
-    // should wait APK to be dm-verity mounted by apkdmverity
+// Verify payload before executing it. Full verification (which is slow) is done when the roothash
+// values from the idsig file and the instance disk are different. This function returns the
+// verified roothash that can be saved to the instance disk.
+fn verify_payload(roothash: Option<&Roothash>) -> Result<Box<Roothash>> {
+    let start_time = SystemTime::now();
+
+    let roothash_from_idsig = get_apk_roothash_from_idsig()?;
+    let roothash_trustful = roothash == Some(&roothash_from_idsig);
+
+    // If roothash can be trusted, pass it to apkdmverity so that it uses the passed roothash
+    // instead of the value read from the idsig file.
+    if roothash_trustful {
+        let roothash = to_hex_string(roothash.unwrap());
+        system_properties::write("microdroid_manager.apk_roothash", &roothash)?;
+    }
+
+    // Start apkdmverity and wait for the dm-verify block
+    system_properties::write("ctl.start", "apkdmverity")?;
     ioutil::wait_for_file(DM_MOUNTED_APK_PATH, WAIT_TIMEOUT)?;
-    verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?;
 
-    info!("payload verification succeeded.");
-    // TODO(jooyung): collect public keys and store them in instance.img
+    // Do the full verification if the roothash is un-trustful. This requires the full scanning of
+    // the APK file and therefore can be very slow if the APK is large. Note that this step is
+    // taken only when the roothash 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
+    if !roothash_trustful {
+        verify(DM_MOUNTED_APK_PATH).context(format!("failed to verify {}", DM_MOUNTED_APK_PATH))?;
+    }
+
+    info!("payload verification successful. took {:#?}", start_time.elapsed().unwrap());
+
+    // At this point, we can ensure that the roothash from the idsig file is trusted, either by
+    // fully verifying the APK or by comparing it with the saved roothash.
+    Ok(roothash_from_idsig)
+}
+
+// Waits until linker config is generated
+fn wait_for_apex_config_done() -> Result<()> {
+    let mut prop = PropertyWatcher::new(APEX_CONFIG_DONE_PROP)?;
+    loop {
+        prop.wait()?;
+        let val = system_properties::read(APEX_CONFIG_DONE_PROP)?;
+        if val == "true" {
+            break;
+        }
+    }
     Ok(())
 }
 
-fn get_apk_roothash() -> Result<Box<[u8]>> {
+fn get_apk_roothash_from_idsig() -> Result<Box<Roothash>> {
     let mut idsig = File::open("/dev/block/by-name/microdroid-apk-idsig")?;
     let idsig = V4Signature::from(&mut idsig)?;
     Ok(idsig.hashing_info.raw_root_hash)