APK roothash is trusted

This CL completes the APK verification story in microdroid. Previously,
the roothash of an APK that apkdmverity uses came from the idsig file.
That file (and thus roothash in it) is untrusted because it's not signed
by anyone. It is generated by virtualization service when the VM is
created.

With this CL, the roothash becomes trustful. Specifically, the roothash
is from the instance disk which is encrypted and signed using the per-VM
secret key. When the roothash in the instance disk is none, which
could happen during the initial boot of the VM, we do the full APK verification (by
scanning every bits), and save the roothash in the instance disk. In the
subsequent boots, we skip the full APK verification, but instead compare
the roothash with the saved one. If they differ, the boot is halted.

1) The start of apkdmverity and zipfuse is controlled by
microdroid_manager. This is to NOT start them before the roothash is
read from the instance disk. Previously, this was impossible because
they are started by init while microdroid_manager is running in
background.

2) apkdmverity now uses the bootstrap bionic libraries, because it is
started far before APEXd activates APEXes.

3) microdroid_manager passes the roothash (read from instance disk) to
apkdmverity via a new system property `microdroid_manager.apk_roothash`.
This is preferred over to letting microdroid_manager directly execute
apkdmverity and pass the roothash as a commandline argument. We don't
want to allow microdroid_manager to fork/exec an executable other than
app payload; there already is a selinux neverallow rule for it.

4) microdroid_manager waits for a new sysprop `linkerconfig.ready` to
become `true` before it executes an app payload. Previously, this was
implied because microdroid_manager waits for /mnt/apk which is created
by zipfuse which in turn is executed after the linkerconfig is ready.
Since zipfuse now is started much earlier, we no longer can rely on the
implicit dependency.

Bug: 193504400
Test: atest MicrodroidHostTestCases
Test: run `adb shell /apex/com.android.virt/bin/vm run-app
/data/local/tmp/virt/MicrodroidDemoApp.apk
/data/local/tmp/virt/MicrodroidDemoApp.apk.idsig
/data/local/tmp/virt/instance.img assets/vm_config.json`

... two times.

In the first run:

microdroid_manager[128]: payload verification successful. took 85.705852ms
microdroid_manager[128]: Updating APK roothash: A4BC793C78E1A...

In the second run:

microdroid_manager[128]: payload verification successful. took 56.789795ms
microdroid_manager[128]: Saved roothash is trustful. Not updating

When the same command is invoked after the apk is intentionally
modified, it fails as expected:

init: Service 'microdroid_manager' (pid 128) exited with status 1
oneshot service took 0.202000 seconds in background

Bug: 193504400
Change-Id: I469116d806cf3dae66fe41c04fdfd6bdb843edab
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)