Merge "Add test to assert that encrypted storage is mounted with NO_EXEC"
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index df8c91e..497c35e 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -87,7 +87,7 @@
     /**
      * Returns the attestation certificate chain of the current VM. The result is in the form of a
      * CBOR encoded Boot Certificate Chain (BCC) as defined in
-     * hardware/interfaces/security/dice/aidl/android/hardware/security/dice/Bcc.aidl.
+     * hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
      */
     byte[] getAttestationChain();
 
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index 35947d7..05bc093 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -12,6 +12,7 @@
         "compos_aidl_interface-rust",
         "libanyhow",
         "libbinder_rs",
+        "libglob",
         "liblazy_static",
         "liblog_rust",
         "libnested_virt",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 92c9a3c..96c8147 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -27,12 +27,13 @@
     VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
     VirtualMachineConfig::VirtualMachineConfig,
 };
-use anyhow::{bail, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
 use binder::{ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use glob::glob;
 use log::{info, warn};
 use rustutils::system_properties;
-use std::fs::{self, File};
+use std::fs::File;
 use std::path::{Path, PathBuf};
 use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
 
@@ -194,15 +195,19 @@
     // Our config APK will be in a directory under app, but the name of the directory is at the
     // discretion of the build system. So just look in each sub-directory until we find it.
     // (In practice there will be exactly one directory, so this shouldn't take long.)
-    let app_dir = apex_dir.join("app");
-    for dir in fs::read_dir(app_dir).context("Reading app dir")? {
-        let apk_file = dir?.path().join("CompOSPayloadApp.apk");
-        if apk_file.is_file() {
-            return Ok(apk_file);
-        }
+    let app_glob = apex_dir.join("app").join("**").join("CompOSPayloadApp*.apk");
+    let mut entries: Vec<PathBuf> =
+        glob(app_glob.to_str().ok_or_else(|| anyhow!("Invalid path: {}", app_glob.display()))?)
+            .context("failed to glob")?
+            .filter_map(|e| e.ok())
+            .collect();
+    if entries.len() > 1 {
+        bail!("Found more than one apk matching {}", app_glob.display());
     }
-
-    bail!("Failed to locate CompOSPayloadApp.apk")
+    match entries.pop() {
+        Some(path) => Ok(path),
+        None => Err(anyhow!("No apks match {}", app_glob.display())),
+    }
 }
 
 fn prepare_idsig(
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 668d7dc..93e65db 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -31,11 +31,13 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.sysprop.HypervisorProperties;
 import android.system.virtualizationservice.VirtualMachineAppConfig;
 import android.system.virtualizationservice.VirtualMachinePayloadConfig;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -47,6 +49,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
+import java.util.zip.ZipFile;
 
 /**
  * Represents a configuration of a virtual machine. A configuration consists of hardware
@@ -57,6 +60,7 @@
  */
 @SystemApi
 public final class VirtualMachineConfig {
+    private static final String TAG = "VirtualMachineConfig";
     private static final String[] EMPTY_STRING_ARRAY = {};
 
     // These define the schema of the config file persisted on disk.
@@ -296,8 +300,7 @@
 
     /**
      * Returns the absolute path of the APK which should contain the binary payload that will
-     * execute within the VM. Returns null if no specific path has been set, so the primary APK will
-     * be used.
+     * execute within the VM. Returns null if no specific path has been set.
      *
      * @hide
      */
@@ -445,18 +448,7 @@
             throws VirtualMachineException {
         VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig();
 
-        String apkPath = mApkPath;
-        if (apkPath == null) {
-            try {
-                ApplicationInfo appInfo =
-                        packageManager.getApplicationInfo(
-                                mPackageName, PackageManager.ApplicationInfoFlags.of(0));
-                // This really is the path to the APK, not a directory.
-                apkPath = appInfo.sourceDir;
-            } catch (PackageManager.NameNotFoundException e) {
-                throw new VirtualMachineException("Package not found", e);
-            }
-        }
+        String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager);
 
         try {
             vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY);
@@ -495,6 +487,45 @@
         return vsConfig;
     }
 
+    private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException {
+        ApplicationInfo appInfo;
+        try {
+            appInfo =
+                    packageManager.getApplicationInfo(
+                            mPackageName, PackageManager.ApplicationInfoFlags.of(0));
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new VirtualMachineException("Package not found", e);
+        }
+
+        String[] splitApkPaths = appInfo.splitSourceDirs;
+        String[] abis = Build.SUPPORTED_64_BIT_ABIS;
+
+        // If there are split APKs, and we know the payload binary name, see if we can find a
+        // split APK containing the binary.
+        if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) {
+            String[] libraryNames = new String[abis.length];
+            for (int i = 0; i < abis.length; i++) {
+                libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName;
+            }
+
+            for (String path : splitApkPaths) {
+                try (ZipFile zip = new ZipFile(path)) {
+                    for (String name : libraryNames) {
+                        if (zip.getEntry(name) != null) {
+                            Log.i(TAG, "Found payload in " + path);
+                            return path;
+                        }
+                    }
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to scan split APK: " + path, e);
+                }
+            }
+        }
+
+        // This really is the path to the APK, not a directory.
+        return appInfo.sourceDir;
+    }
+
     private int bytesToMebiBytes(long mMemoryBytes) {
         long oneMebi = 1024 * 1024;
         // We can't express requests for more than 2 exabytes, but then they're not going to succeed
@@ -596,7 +627,8 @@
 
         /**
          * Sets the absolute path of the APK containing the binary payload that will execute within
-         * the VM. If not set explicitly, defaults to the primary APK of the context.
+         * the VM. If not set explicitly, defaults to the split APK containing the payload, if there
+         * is one, and otherwise the primary APK of the context.
          *
          * @hide
          */
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 18cf49d..495d3bb 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -19,9 +19,9 @@
         "libbinder_rs",
         "libbyteorder",
         "libcap_rust",
+        "libciborium",
         "libdiced_open_dice",
         "libdiced_sample_inputs",
-        "libdiced_utils",
         "libglob",
         "libhex",
         "libitertools",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index c3136e8..3a2a1e6 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -16,12 +16,14 @@
 
 use anyhow::{anyhow, bail, Context, Error, Result};
 use byteorder::{NativeEndian, ReadBytesExt};
+use ciborium::{cbor, ser};
 use diced_open_dice::{
     bcc_handover_parse, retry_bcc_main_flow, BccHandover, Config, DiceArtifacts, DiceMode, Hash,
     Hidden, InputValues, OwnedDiceArtifacts,
 };
 use keystore2_crypto::ZVec;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
+use microdroid_metadata::PayloadMetadata;
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
 use std::fs;
@@ -157,3 +159,70 @@
         }
     }
 }
+
+/// Returns a configuration descriptor of the given payload following the BCC's specification:
+/// https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
+/// {
+///   -70002: "Microdroid payload",
+///   ? -71000: tstr // payload_config_path
+///   ? -71001: PayloadConfig
+/// }
+/// PayloadConfig = {
+///   1: tstr // payload_binary_name
+/// }
+pub fn format_payload_config_descriptor(payload_metadata: &PayloadMetadata) -> Result<Vec<u8>> {
+    const MICRODROID_PAYLOAD_COMPONENT_NAME: &str = "Microdroid payload";
+
+    let config_descriptor_cbor_value = match payload_metadata {
+        PayloadMetadata::config_path(payload_config_path) => cbor!({
+            -70002 => MICRODROID_PAYLOAD_COMPONENT_NAME,
+            -71000 => payload_config_path
+        }),
+        PayloadMetadata::config(payload_config) => cbor!({
+            -70002 => MICRODROID_PAYLOAD_COMPONENT_NAME,
+            -71001 => {1 => payload_config.payload_binary_name}
+        }),
+    }
+    .context("Failed to build a CBOR Value from payload metadata")?;
+    let mut config_descriptor = Vec::new();
+    ser::into_writer(&config_descriptor_cbor_value, &mut config_descriptor)?;
+    Ok(config_descriptor)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use microdroid_metadata::PayloadConfig;
+
+    #[test]
+    fn payload_metadata_with_path_formats_correctly() -> Result<()> {
+        let payload_metadata = PayloadMetadata::config_path("/config_path".to_string());
+        let config_descriptor = format_payload_config_descriptor(&payload_metadata)?;
+        static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
+            0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
+            0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
+            0x15, 0x57, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74,
+            0x68,
+        ];
+        assert_eq!(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
+        Ok(())
+    }
+
+    #[test]
+    fn payload_metadata_with_config_formats_correctly() -> Result<()> {
+        let payload_config = PayloadConfig {
+            payload_binary_name: "payload_binary".to_string(),
+            ..Default::default()
+        };
+        let payload_metadata = PayloadMetadata::config(payload_config);
+        let config_descriptor = format_payload_config_descriptor(&payload_metadata)?;
+        static EXPECTED_CONFIG_DESCRIPTOR: &[u8] = &[
+            0xa2, 0x3a, 0x00, 0x01, 0x11, 0x71, 0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72,
+            0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x00, 0x01,
+            0x15, 0x58, 0xa1, 0x01, 0x6e, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x62,
+            0x69, 0x6e, 0x61, 0x72, 0x79,
+        ];
+        assert_eq!(EXPECTED_CONFIG_DESCRIPTOR, &config_descriptor);
+        Ok(())
+    }
+}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index a464163..f83753c 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -21,7 +21,7 @@
 mod swap;
 mod vm_payload_service;
 
-use crate::dice::{DiceDriver, derive_sealing_key};
+use crate::dice::{DiceDriver, derive_sealing_key, format_payload_config_descriptor};
 use crate::instance::{ApexData, ApkData, InstanceDisk, MicrodroidData, RootHash};
 use crate::vm_payload_service::register_vm_payload_service;
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::ErrorCode::ErrorCode;
@@ -35,7 +35,6 @@
 use apkverify::{get_public_key_der, verify, V4Signature};
 use binder::Strong;
 use diced_open_dice::OwnedDiceArtifacts;
-use diced_utils::cbor::{encode_header, encode_number};
 use glob::glob;
 use itertools::sorted;
 use libc::VMADDR_CID_HOST;
@@ -287,54 +286,14 @@
     let code_hash = code_hash_ctx.finish();
     let authority_hash = authority_hash_ctx.finish();
 
-    // {
-    //   -70002: "Microdroid payload",
-    //   ? -71000: tstr // payload_config_path
-    //   ? -71001: PayloadConfig
-    // }
-    // PayloadConfig = {
-    //   1: tstr // payload_binary_name
-    // }
-
-    let mut config_desc = vec![
-        0xa2, // map(2)
-        0x3a, 0x00, 0x01, 0x11, 0x71, // -70002
-        0x72, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x20, 0x70, 0x61, 0x79,
-        0x6c, 0x6f, 0x61, 0x64, // "Microdroid payload"
-    ];
-
-    match payload_metadata {
-        PayloadMetadata::config_path(payload_config_path) => {
-            encode_negative_number(-71000, &mut config_desc)?;
-            encode_tstr(payload_config_path, &mut config_desc)?;
-        }
-        PayloadMetadata::config(payload_config) => {
-            encode_negative_number(-71001, &mut config_desc)?;
-            encode_header(5, 1, &mut config_desc)?; // map(1)
-            encode_number(1, &mut config_desc)?;
-            encode_tstr(&payload_config.payload_binary_name, &mut config_desc)?;
-        }
-    }
+    let config_descriptor = format_payload_config_descriptor(payload_metadata)?;
 
     // Check debuggability, conservatively assuming it is debuggable
     let debuggable = system_properties::read_bool(DEBUGGABLE_PROP, true)?;
 
     // Send the details to diced
     let hidden = verified_data.salt.clone().try_into().unwrap();
-    dice.derive(code_hash, &config_desc, authority_hash, debuggable, hidden)
-}
-
-fn encode_tstr(tstr: &str, buffer: &mut Vec<u8>) -> Result<()> {
-    let bytes = tstr.as_bytes();
-    encode_header(3, bytes.len().try_into().unwrap(), buffer)?;
-    buffer.extend_from_slice(bytes);
-    Ok(())
-}
-
-fn encode_negative_number(n: i64, buffer: &mut dyn Write) -> Result<()> {
-    ensure!(n < 0);
-    let n = -1 - n;
-    encode_header(1, n.try_into().unwrap(), buffer)
+    dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
 }
 
 fn is_strict_boot() -> bool {
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 5d785de..36edc64 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -152,7 +152,7 @@
 }
 
 fn find_empty_payload_apk_path() -> Result<PathBuf, Error> {
-    const GLOB_PATTERN: &str = "/apex/com.android.virt/app/**/EmptyPayloadApp.apk";
+    const GLOB_PATTERN: &str = "/apex/com.android.virt/app/**/EmptyPayloadApp*.apk";
     let mut entries: Vec<PathBuf> =
         glob(GLOB_PATTERN).context("failed to glob")?.filter_map(|e| e.ok()).collect();
     if entries.len() > 1 {