Merge "sed setMaxOutgoing{Threads,Connections}"
diff --git a/authfs/src/file/dir.rs b/authfs/src/file/dir.rs
index f3cc6f8..5d2ec9f 100644
--- a/authfs/src/file/dir.rs
+++ b/authfs/src/file/dir.rs
@@ -28,7 +28,7 @@
 use crate::fsverity::VerifiedFileEditor;
 use crate::fusefs::{AuthFsDirEntry, Inode};
 
-const MAX_ENTRIES: u16 = 100; // Arbitrary limit
+const MAX_ENTRIES: u16 = 1000; // Arbitrary limit
 
 struct InodeInfo {
     inode: Inode,
diff --git a/authfs/tests/benchmarks/Android.bp b/authfs/tests/benchmarks/Android.bp
index 9bdef7b..38ece79 100644
--- a/authfs/tests/benchmarks/Android.bp
+++ b/authfs/tests/benchmarks/Android.bp
@@ -23,7 +23,6 @@
         ":authfs_test_files",
         ":CtsApkVerityTestPrebuiltFiles",
         ":MicrodroidTestApp",
-        ":measure_io",
     ],
 }
 
@@ -36,3 +35,19 @@
         "libbase",
     ],
 }
+
+// Package measure_io binary into a jar, to bundle with the MicrodroidTestApp.
+// When MicrodroidTestApp is mounted inside the Microdroid, the zipfuse will
+// add the +x permission on it.
+java_genrule {
+    name: "measure_io_as_jar",
+    out: ["measure_io.jar"],
+    srcs: [
+        ":measure_io",
+    ],
+    cmd: "out_dir=$$(dirname $(out))" +
+        "&& bin_dir=\"bin\" " +
+        "&& mkdir -p $$out_dir/$$bin_dir" +
+        "&& cp $(in) $$out_dir/$$bin_dir" +
+        "&& jar cf $(out) -C $$out_dir $$bin_dir",
+}
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 32eafb8..085d06e 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -18,7 +18,6 @@
 
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 
-import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -45,7 +44,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -57,11 +55,8 @@
 public class AuthFsBenchmarks extends BaseHostJUnit4Test {
     private static final int TRIAL_COUNT = 5;
 
-    /** Name of the measure_io binary on host. */
-    private static final String MEASURE_IO_BIN_NAME = "measure_io";
-
     /** Path to measure_io on Microdroid. */
-    private static final String MEASURE_IO_BIN_PATH = "/data/local/tmp/measure_io";
+    private static final String MEASURE_IO_BIN_PATH = "/mnt/apk/bin/measure_io";
 
     /** fs-verity digest (sha256) of testdata/input.4m */
     private static final String DIGEST_4M =
@@ -123,7 +118,6 @@
     }
 
     private void readRemoteFile(String mode) throws DeviceNotAvailableException {
-        pushMeasureIoBinToMicrodroid();
         // Cache the file in memory for the host.
         mAuthFsTestRule
                 .getAndroid()
@@ -146,7 +140,6 @@
     }
 
     private void writeRemoteFile(String mode) throws DeviceNotAvailableException {
-        pushMeasureIoBinToMicrodroid();
         String filePath = mAuthFsTestRule.MOUNT_DIR + "/5";
         int fileSizeMb = 8;
         String cmd = MEASURE_IO_BIN_PATH + " " + filePath + " " + fileSizeMb + " " + mode + " w";
@@ -165,14 +158,6 @@
         reportMetrics(rates, mode + "_write", "mb_per_sec");
     }
 
-    private void pushMeasureIoBinToMicrodroid() throws DeviceNotAvailableException {
-        File measureReadBin = mAuthFsTestRule.findTestFile(getBuild(), MEASURE_IO_BIN_NAME);
-        assertThat(measureReadBin.exists()).isTrue();
-        mAuthFsTestRule.getMicrodroidDevice().pushFile(measureReadBin, MEASURE_IO_BIN_PATH);
-        assertThat(mAuthFsTestRule.getMicrodroid().run("ls " + MEASURE_IO_BIN_PATH))
-                .isEqualTo(MEASURE_IO_BIN_PATH);
-    }
-
     private void reportMetrics(List<Double> metrics, String name, String unit) {
         Map<String, Double> stats = mMetricsProcessor.computeStats(metrics, name, unit);
         for (Map.Entry<String, Double> entry : stats.entrySet()) {
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/docs/debug/ramdump.md b/docs/debug/ramdump.md
index 771c608..020f054 100644
--- a/docs/debug/ramdump.md
+++ b/docs/debug/ramdump.md
@@ -73,8 +73,8 @@
 Download the source code and build it as follows. This needs to be done only once.
 
 ```shell
-$ wget https://github.com/crash-utility/crash/archive/refs/tags/8.0.1.tar.gz -O - | tar xzvf
-$ make -C crash-8.0.1 target=ARM64
+$ wget https://github.com/crash-utility/crash/archive/refs/tags/8.0.2.tar.gz -O - | tar xzv
+$ make -j -C crash-8.0.2 target=ARM64
 ```
 
 ### Obtaining vmlinux
@@ -101,7 +101,7 @@
 ### Running crash(8) with the RAM dump and the kernel image
 
 ```shell
-$ crash-8.0.1/crash ramdump vmlinux
+$ crash-8.0.2/crash ramdump vmlinux
 ```
 
 You can now analyze the RAM dump using the various commands that crash(8) provides. For example, `bt <pid>` command shows the stack trace of a process.
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index a6b3ed6..5f39b1c 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -1033,11 +1033,13 @@
             }
             checkStopped();
 
-            // Delete any existing file before recreating; that ensures any VirtualMachineDescriptor
-            // that refers to the old file does not see the new config.
-            mConfigFilePath.delete();
-            newConfig.serialize(mConfigFilePath);
-            mConfig = newConfig;
+            if (oldConfig != newConfig) {
+                // Delete any existing file before recreating; that ensures any
+                // VirtualMachineDescriptor that refers to the old file does not see the new config.
+                mConfigFilePath.delete();
+                newConfig.serialize(mConfigFilePath);
+                mConfig = newConfig;
+            }
             return oldConfig;
         }
     }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index c364b42..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
      */
@@ -421,6 +424,9 @@
      */
     @SystemApi
     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
+        if (this == other) {
+            return true;
+        }
         return this.mDebugLevel == other.mDebugLevel
                 && this.mProtectedVm == other.mProtectedVm
                 && this.mEncryptedStorageBytes == other.mEncryptedStorageBytes
@@ -442,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);
@@ -492,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
@@ -593,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/Android.bp b/microdroid/Android.bp
index dc59fff..bc4db6c 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -85,13 +85,6 @@
         "microdroid_property_contexts",
         "mke2fs.microdroid",
 
-        // Adding more libs manually for unbundled build.
-        // TODO(b/268557568) these should be added automatically.
-        "libcrypto",
-        "liblzma",
-        "libc++",
-        "libssl",
-
         "libvm_payload", // used by payload to interact with microdroid manager
 
         "prng_seeder_microdroid",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index ce0cab4..70c22d4 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -98,7 +98,7 @@
     mount rootfs rootfs / remount bind ro nodev
 
     # TODO(b/185767624): change the hard-coded size?
-    mount tmpfs tmpfs /data noatime nosuid nodev rw size=128M
+    mount tmpfs tmpfs /data noatime nosuid nodev noexec rw size=128M
 
     # We chown/chmod /data again so because mount is run as root + defaults
     chown system system /data
diff --git a/microdroid/kdump/kernel/arm64/kernel-5.15 b/microdroid/kdump/kernel/arm64/kernel-5.15
index 0f2172b..28b0214 100644
--- a/microdroid/kdump/kernel/arm64/kernel-5.15
+++ b/microdroid/kdump/kernel/arm64/kernel-5.15
Binary files differ
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 1148c31..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 {
@@ -721,6 +680,9 @@
     // Use the salt from a verified instance, or generate a salt for a new instance.
     let salt = if let Some(saved_data) = saved_data {
         saved_data.salt.clone()
+    } else if is_strict_boot() {
+        // No need to add more entropy as a previous stage must have used a new, random salt.
+        vec![0u8; 64]
     } else {
         let mut salt = vec![0u8; 64];
         salt.as_mut_slice().try_fill(&mut rand::thread_rng())?;
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index f209784..f62a580 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -216,12 +216,22 @@
     }
 
     /// Get slice containing the platform BCC.
-    pub fn get_bcc_mut(&mut self) -> &mut [u8] {
-        &mut self.body[self.bcc_range.clone()]
-    }
+    pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
+        let bcc_start = self.bcc_range.start;
+        let bcc_end = self.bcc_range.len();
+        let (_, rest) = self.body.split_at_mut(bcc_start);
+        let (bcc, rest) = rest.split_at_mut(bcc_end);
 
-    /// Get slice containing the platform debug policy.
-    pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
-        self.dp_range.as_ref().map(|r| &mut self.body[r.clone()])
+        let dp = if let Some(dp_range) = &self.dp_range {
+            let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
+            let dp_end = dp_range.len();
+            let (_, rest) = rest.split_at_mut(dp_start);
+            let (dp, _) = rest.split_at_mut(dp_end);
+            Some(dp)
+        } else {
+            None
+        };
+
+        (bcc, dp)
     }
 }
diff --git a/pvmfw/src/debug_policy.rs b/pvmfw/src/debug_policy.rs
index 4cb338d..23d3e1d 100644
--- a/pvmfw/src/debug_policy.rs
+++ b/pvmfw/src/debug_policy.rs
@@ -203,7 +203,7 @@
         disable_ramdump(fdt)?;
     }
 
-    // Handles conseole output in the debug policy
+    // Handles console output in the debug policy
     if is_console_output_enabled(fdt)? {
         enable_console_output(fdt)?;
         info!("console output is enabled by debug policy");
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index a1cd2a1..106a4ef 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -242,7 +242,7 @@
         RebootReason::InvalidConfig
     })?;
 
-    let bcc_slice = appended.get_bcc_mut();
+    let (bcc_slice, debug_policy) = appended.get_entries();
 
     debug!("Activating dynamic page table...");
     // SAFETY - page_table duplicates the static mappings for everything that the Rust code is
@@ -266,7 +266,7 @@
 
     // SAFETY - As we `?` the result, there is no risk of using a bad `slices.fdt`.
     unsafe {
-        handle_debug_policy(slices.fdt, appended.get_debug_policy()).map_err(|e| {
+        handle_debug_policy(slices.fdt, debug_policy).map_err(|e| {
             error!("Unexpected error when handling debug policy: {e:?}");
             RebootReason::from(e)
         })?;
@@ -397,17 +397,10 @@
         }
     }
 
-    fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
+    fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
         match self {
-            Self::Config(ref mut cfg) => cfg.get_debug_policy(),
-            Self::LegacyBcc(_) => None,
-        }
-    }
-
-    fn get_bcc_mut(&mut self) -> &mut [u8] {
-        match self {
-            Self::LegacyBcc(ref mut bcc) => bcc,
-            Self::Config(ref mut cfg) => cfg.get_bcc_mut(),
+            Self::Config(ref mut cfg) => cfg.get_entries(),
+            Self::LegacyBcc(ref mut bcc) => (bcc, None),
         }
     }
 }
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 48bab0c..d89e718 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -121,7 +121,7 @@
     })?;
     flush(next_bcc);
 
-    let strict_boot = false; // TODO(b/268307476): Flip in its own commit to isolate testing.
+    let strict_boot = true;
     modify_for_next_stage(fdt, next_bcc, new_instance, strict_boot).map_err(|e| {
         error!("Failed to configure device tree: {e}");
         RebootReason::InternalError
diff --git a/tests/aidl/Android.bp b/tests/aidl/Android.bp
index d59ca7e..ed4e8ff 100644
--- a/tests/aidl/Android.bp
+++ b/tests/aidl/Android.bp
@@ -6,6 +6,10 @@
     name: "com.android.microdroid.testservice",
     srcs: ["com/android/microdroid/testservice/**/*.aidl"],
     unstable: true,
+    flags: [
+        "-Werror",
+        "-Wno-mixed-oneway",
+    ],
     backend: {
         java: {
             gen_rpc: true,
diff --git a/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl b/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl
new file mode 100644
index 0000000..9859090
--- /dev/null
+++ b/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.microdroid.testservice;
+
+import com.android.microdroid.testservice.IVmCallback;
+
+/**
+ * An interface exposed by the app for callbacks from the VM.
+ *
+ * {@hide}
+ */
+interface IAppCallback {
+    /** Invites the app to call vmCallback#echoMessage() */
+    void setVmCallback(IVmCallback vmCallback);
+
+    /** Asynchronusly called by the VM in response to a call to echoMessage(). */
+    void onEchoRequestReceived(String message);
+}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 4611134..36c3aaf 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -15,7 +15,12 @@
  */
 package com.android.microdroid.testservice;
 
-/** {@hide} */
+import com.android.microdroid.testservice.IAppCallback;
+
+/**
+ * This is the service exposed by the test payload, called by the test app.
+ * {@hide}
+ */
 interface ITestService {
     const long SERVICE_PORT = 5678;
 
@@ -56,6 +61,15 @@
     /* get the content of the specified file. */
     String readFromFile(String path);
 
+    /* get file permissions of the give file by stat'ing it */
+    int getFilePermissions(String path);
+
+    /** Returns flags for the given mountPoint. */
+    int getMountFlags(String mountPoint);
+
+    /** Requests the VM to asynchronously call appCallback.setVmCallback() */
+    void requestCallback(IAppCallback appCallback);
+
     /**
      * Request the service to exit, triggering the termination of the VM. This may cause any
      * requests in flight to fail.
diff --git a/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl b/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl
new file mode 100644
index 0000000..617d184
--- /dev/null
+++ b/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.microdroid.testservice;
+
+/**
+ * An interface exposed by the VM for callbacks from the app.
+ *
+ * {@hide}
+ */
+interface IVmCallback {
+    /** Requests the VM to asynchronously call the app's onEchoRequestReceived() callback. */
+    void echoMessage(String message);
+}
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
index ba82c38..8a63578 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/DeviceProperties.java
@@ -20,12 +20,14 @@
 
 /** This class can be used in both host tests and device tests to get the device properties. */
 public final class DeviceProperties {
+
     /** PropertyGetter is used to get the property associated to a given key. */
     public interface PropertyGetter {
         String getProperty(String key) throws Exception;
     }
 
     private static final String KEY_VENDOR_DEVICE = "ro.product.vendor.device";
+    private static final String KEY_BOARD_PLATFORM = "ro.board.platform";
     private static final String KEY_BUILD_TYPE = "ro.build.type";
     private static final String KEY_METRICS_TAG = "debug.hypervisor.metrics_tag";
 
@@ -51,6 +53,11 @@
         return vendorDeviceName != null && vendorDeviceName.startsWith(CUTTLEFISH_DEVICE_PREFIX);
     }
 
+    public boolean isGs101() {
+        String platform = getProperty(KEY_BOARD_PLATFORM);
+        return "gs101".equals(platform);
+    }
+
     /**
      * @return whether the device is user build.
      */
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index be3c1da..bff16a2 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -53,15 +53,27 @@
 import java.util.concurrent.TimeUnit;
 
 public abstract class MicrodroidDeviceTestBase {
+    private static final String TAG = "MicrodroidDeviceTestBase";
     private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP";
 
     public static boolean isCuttlefish() {
-        return DeviceProperties.create(SystemProperties::get).isCuttlefish();
+        return getDeviceProperties().isCuttlefish();
+    }
+
+    public static boolean isGs101() {
+        return getDeviceProperties().isGs101();
+    }
+
+    public static boolean isUserBuild() {
+        return getDeviceProperties().isUserBuild();
     }
 
     public static String getMetricPrefix() {
-        return MetricsProcessor.getMetricPrefix(
-                DeviceProperties.create(SystemProperties::get).getMetricsTag());
+        return MetricsProcessor.getMetricPrefix(getDeviceProperties().getMetricsTag());
+    }
+
+    private static DeviceProperties getDeviceProperties() {
+        return DeviceProperties.create(SystemProperties::get);
     }
 
     protected final void grantPermission(String permission) {
@@ -82,13 +94,22 @@
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
         String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE;
-        String out = runInShell("MicrodroidDeviceTestBase", uiAutomation, cmd).trim();
+        String out = runInShell(TAG, uiAutomation, cmd).trim();
         String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!";
         if (!expect.equals(out)) {
             throw new IOException("Could not apply max performance task profile: " + out);
         }
     }
 
+    public final boolean getDebugPolicyBoolean(String debugPolicy) throws IOException {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        String debugPolicyFilePath = "/proc/device-tree" + debugPolicy;
+        String cmd = "su root xxd -p " + debugPolicyFilePath;
+        String dp = runInShell(TAG, uiAutomation, cmd).trim();
+        return "00000001".equals(dp);
+    }
+
     private Context mCtx;
     private boolean mProtectedVm;
 
@@ -443,6 +464,8 @@
         public String mFileContent;
         public byte[] mBcc;
         public long[] mTimings;
+        public int mFileMode;
+        public int mMountFlags;
 
         public void assertNoException() {
             if (mException != null) {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 0be6a62..1fa0afe 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -827,6 +827,7 @@
         assumeTrue(
                 "Protected VMs are not supported",
                 getAndroidDevice().supportsMicrodroid(/*protectedVm=*/ true));
+        assumeTrue("Test requires adb unroot", getDevice().disableAdbRoot());
         CommandRunner android = new CommandRunner(getDevice());
 
         // Pull etc/microdroid.json
diff --git a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
index 1ada5a1..755613a 100644
--- a/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/PvmfwDebugPolicyHostTests.java
@@ -22,6 +22,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeFalse;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -56,7 +57,6 @@
     @NonNull private static final String MICRODROID_DEBUG_FULL = "full";
     @NonNull private static final String MICRODROID_CONFIG_PATH = "assets/vm_config_apex.json";
     @NonNull private static final String MICRODROID_LOG_PATH = TEST_ROOT + "log.txt";
-    @NonNull private static final String MICRODROID_CONSOLE_PATH = TEST_ROOT + "console.txt";
     private static final int BOOT_COMPLETE_TIMEOUT_MS = 30000; // 30 seconds
     private static final int CONSOLE_OUTPUT_WAIT_MS = 5000; // 5 seconds
 
@@ -92,6 +92,7 @@
         assumeTrue(
                 "Skip if protected VMs are not supported",
                 mAndroidDevice.supportsMicrodroid(/* protectedVm= */ true));
+        assumeFalse("Test requires setprop for using custom pvmfw and adb root", isUserBuild());
 
         mAndroidDevice.enableAdbRoot();
 
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index bafab53..fe8f5c9 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -31,6 +31,7 @@
         "cbor-java",
         "truth-prebuilt",
         "compatibility-common-util-devicesidelib",
+        "measure_io_as_jar",
     ],
     jni_libs: [
         "MicrodroidTestNativeLib",
@@ -62,6 +63,7 @@
     static_libs: [
         "com.android.microdroid.testservice-ndk",
         "libbase",
+        "libfstab",
         "libfsverity_digests_proto_cc",
         "liblog",
         "libprotobuf-cpp-lite-ndk",
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index f3f1252..6e4694c 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -28,6 +28,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.TruthJUnit.assume;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.base.Strings;
@@ -45,17 +46,21 @@
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.SystemProperties;
+import android.system.OsConstants;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineDescriptor;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.microdroid.test.vmshare.IVmShareTestService;
+import com.android.microdroid.testservice.IAppCallback;
 import com.android.microdroid.testservice.ITestService;
+import com.android.microdroid.testservice.IVmCallback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -133,7 +138,7 @@
     private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
 
     private void createAndConnectToVmHelper(int cpuTopology) throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -178,7 +183,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void createAndRunNoDebugVm() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         // For most of our tests we use a debug VM so failures can be diagnosed.
         // But we do need non-debug VMs to work, so run one.
@@ -205,7 +210,7 @@
                 "9.17/C-1-4",
             })
     public void createVmRequiresPermission() {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
 
@@ -226,7 +231,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void autoCloseVm() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -311,7 +316,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void vmLifecycleChecks() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -360,7 +365,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void connectVsock() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -398,6 +403,59 @@
 
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
+    public void binderCallbacksWork() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setMemoryBytes(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+
+        String request = "Hello";
+        CompletableFuture<String> response = new CompletableFuture<>();
+
+        IAppCallback appCallback =
+                new IAppCallback.Stub() {
+                    @Override
+                    public void setVmCallback(IVmCallback vmCallback) {
+                        // Do this on a separate thread to simulate an asynchronous trigger,
+                        // and to make sure it doesn't happen in the context of an inbound binder
+                        // call.
+                        new Thread() {
+                            @Override
+                            public void run() {
+                                try {
+                                    vmCallback.echoMessage(request);
+                                } catch (Exception e) {
+                                    response.completeExceptionally(e);
+                                }
+                            }
+                        }.start();
+                    }
+
+                    @Override
+                    public void onEchoRequestReceived(String message) {
+                        response.complete(message);
+                    }
+                };
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (service, results) -> {
+                            service.requestCallback(appCallback);
+                            response.get(10, TimeUnit.SECONDS);
+                        });
+        testResults.assertNoException();
+        assertThat(response.getNow("no response")).isEqualTo("Received: " + request);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1"})
     public void vmConfigGetAndSetTests() {
         // Minimal has as little as specified as possible; everything that can be is defaulted.
         VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
@@ -578,7 +636,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void vmmGetAndCreate() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -676,7 +734,7 @@
             "9.17/C-1-4",
     })
     public void createVmWithConfigRequiresPermission() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -699,7 +757,7 @@
             "9.17/C-1-1",
     })
     public void deleteVm() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -727,7 +785,7 @@
                 "9.17/C-1-1",
             })
     public void deleteVmFiles() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -760,7 +818,7 @@
             "9.17/C-1-1",
     })
     public void validApkPathIsAccepted() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -797,7 +855,7 @@
             "9.17/C-2-1"
     })
     public void extraApk() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig config =
@@ -865,7 +923,7 @@
     }
 
     private void changeDebugLevel(int fromLevel, int toLevel) throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig.Builder builder =
                 newVmConfigBuilder()
@@ -936,7 +994,7 @@
             "9.17/C-2-7"
     })
     public void instancesOfSameVmHaveDifferentCdis() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig normalConfig =
@@ -962,7 +1020,7 @@
             "9.17/C-2-7"
     })
     public void sameInstanceKeepsSameCdis() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
         assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
@@ -987,7 +1045,7 @@
             "9.17/C-2-7"
     })
     public void bccIsSuperficiallyWellFormed() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig normalConfig =
@@ -1026,7 +1084,7 @@
             "9.17/C-1-2"
     })
     public void accessToCdisIsRestricted() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1226,7 +1284,7 @@
 
     @Test
     public void importedVmAndOriginalVmHaveTheSameCdi() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
         // Arrange
         grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
         VirtualMachineConfig config =
@@ -1325,7 +1383,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void encryptedStorageAvailable() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1349,7 +1407,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void encryptedStorageIsInaccessibleToDifferentVm() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1410,7 +1468,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void microdroidLauncherHasEmptyCapabilities() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         final VirtualMachineConfig vmConfig =
                 newVmConfigBuilder()
@@ -1435,7 +1493,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void encryptedStorageIsPersistent() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1472,7 +1530,7 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void canReadFileFromAssets_debugFull() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1496,7 +1554,7 @@
 
     @Test
     public void outputShouldBeExplicitlyCaptured() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         final VirtualMachineConfig vmConfig =
                 new VirtualMachineConfig.Builder(getContext())
@@ -1517,6 +1575,22 @@
         }
     }
 
+    private boolean isConsoleOutputEnabledByDebugPolicy() {
+        if (isUserBuild()) {
+            Log.i(
+                    TAG,
+                    "Debug policy is inaccessible in user build. Assumes that console output is"
+                            + " disabled");
+            return false;
+        }
+        try {
+            return getDebugPolicyBoolean("/avf/guest/common/log");
+        } catch (IOException e) {
+            Log.w(TAG, "Fail to read debug policy. Assumes false", e);
+            return false;
+        }
+    }
+
     private boolean checkVmOutputIsRedirectedToLogcat(boolean debuggable) throws Exception {
         String time =
                 LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
@@ -1549,21 +1623,27 @@
 
     @Test
     public void outputIsRedirectedToLogcatIfNotCaptured() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
+        assumeFalse(
+                "Debug policy would turn on console output. Perhaps userdebug build?",
+                isConsoleOutputEnabledByDebugPolicy());
 
         assertThat(checkVmOutputIsRedirectedToLogcat(true)).isTrue();
     }
 
     @Test
     public void outputIsNotRedirectedToLogcatIfNotDebuggable() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
+        assumeFalse(
+                "Debug policy would turn on console output. Perhaps userdebug build?",
+                isConsoleOutputEnabledByDebugPolicy());
 
         assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
     }
 
     @Test
     public void testStartVmWithPayloadOfAnotherApp() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         Context ctx = getContext();
         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
@@ -1591,7 +1671,7 @@
 
     @Test
     public void testVmDescriptorParcelUnparcel_noTrustedStorage() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1625,7 +1705,7 @@
 
     @Test
     public void testVmDescriptorParcelUnparcel_withTrustedStorage() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         VirtualMachineConfig config =
                 newVmConfigBuilder()
@@ -1679,7 +1759,7 @@
 
     @Test
     public void testShareVmWithAnotherApp() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         Context ctx = getContext();
         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
@@ -1727,7 +1807,7 @@
 
     @Test
     public void testShareVmWithAnotherApp_encryptedStorage() throws Exception {
-        assumeSupportedKernel();
+        assumeSupportedDevice();
 
         Context ctx = getContext();
         Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
@@ -1779,6 +1859,98 @@
         }
     }
 
+    @Test
+    @CddTest(requirements = {"9.17/C-1-5"})
+    public void testFileUnderBinHasExecutePermission() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setMemoryBytes(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_perms", vmConfig);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mFileMode = ts.getFilePermissions("/mnt/apk/bin/measure_io");
+                        });
+
+        testResults.assertNoException();
+        int allPermissionsMask =
+                OsConstants.S_IRUSR
+                        | OsConstants.S_IWUSR
+                        | OsConstants.S_IXUSR
+                        | OsConstants.S_IRGRP
+                        | OsConstants.S_IWGRP
+                        | OsConstants.S_IXGRP
+                        | OsConstants.S_IROTH
+                        | OsConstants.S_IWOTH
+                        | OsConstants.S_IXOTH;
+        assertThat(testResults.mFileMode & allPermissionsMask)
+                .isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
+    }
+
+    // Taken from bionic/libs/kernel/uapi/linux/mounth.h.
+    private static final int MS_NOEXEC = 8;
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-5"})
+    public void dataIsMountedWithNoExec() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_data_mount", vmConfig);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mMountFlags = ts.getMountFlags("/data");
+                        });
+
+        assertThat(testResults.mException).isNull();
+        assertWithMessage("/data should be mounted with MS_NOEXEC")
+                .that(testResults.mMountFlags & MS_NOEXEC)
+                .isEqualTo(MS_NOEXEC);
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-5"})
+    public void encryptedStoreIsMountedWithNoExec() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setEncryptedStorageBytes(4_000_000)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_encstore_no_exec", vmConfig);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mMountFlags = ts.getMountFlags("/mnt/encryptedstore");
+                        });
+
+        assertThat(testResults.mException).isNull();
+        assertWithMessage("/mnt/encryptedstore should be mounted with MS_NOEXEC")
+                .that(testResults.mMountFlags & MS_NOEXEC)
+                .isEqualTo(MS_NOEXEC);
+    }
+
     private static class VmShareServiceConnection implements ServiceConnection {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
@@ -1848,10 +2020,16 @@
         return 0;
     }
 
-    private void assumeSupportedKernel() {
+    private void assumeSupportedDevice() {
         assume()
                 .withMessage("Skip on 5.4 kernel. b/218303240")
                 .that(KERNEL_VERSION)
                 .isNotEqualTo("5.4");
+
+        if (isProtectedVm()) {
+            assume().withMessage("Protected VMs not supported on gs101 devices. b/270841564")
+                    .that(isGs101())
+                    .isFalse();
+        }
     }
 }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 19d6cd4..d24ddfd 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -15,12 +15,15 @@
  */
 
 #include <aidl/com/android/microdroid/testservice/BnTestService.h>
+#include <aidl/com/android/microdroid/testservice/BnVmCallback.h>
+#include <aidl/com/android/microdroid/testservice/IAppCallback.h>
 #include <android-base/file.h>
 #include <android-base/properties.h>
 #include <android-base/result.h>
 #include <android-base/scopeguard.h>
 #include <android/log.h>
 #include <fcntl.h>
+#include <fstab/fstab.h>
 #include <fsverity_digests.pb.h>
 #include <linux/vm_sockets.h>
 #include <stdint.h>
@@ -40,8 +43,14 @@
 using android::base::make_scope_guard;
 using android::base::Result;
 using android::base::unique_fd;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+using android::fs_mgr::GetEntryForMountPoint;
+using android::fs_mgr::ReadFstabFromFile;
 
 using aidl::com::android::microdroid::testservice::BnTestService;
+using aidl::com::android::microdroid::testservice::BnVmCallback;
+using aidl::com::android::microdroid::testservice::IAppCallback;
 using ndk::ScopedAStatus;
 
 extern void testlib_sub();
@@ -139,7 +148,25 @@
 }
 
 Result<void> start_test_service() {
+    class VmCallbackImpl : public BnVmCallback {
+    private:
+        std::shared_ptr<IAppCallback> mAppCallback;
+
+    public:
+        explicit VmCallbackImpl(const std::shared_ptr<IAppCallback>& appCallback)
+              : mAppCallback(appCallback) {}
+
+        ScopedAStatus echoMessage(const std::string& message) override {
+            std::thread callback_thread{[=, appCallback = mAppCallback] {
+                appCallback->onEchoRequestReceived("Received: " + message);
+            }};
+            callback_thread.detach();
+            return ScopedAStatus::ok();
+        }
+    };
+
     class TestService : public BnTestService {
+    public:
         ScopedAStatus addInteger(int32_t a, int32_t b, int32_t* out) override {
             *out = a + b;
             return ScopedAStatus::ok();
@@ -221,7 +248,7 @@
             return ScopedAStatus::ok();
         }
 
-        virtual ::ScopedAStatus runEchoReverseServer() override {
+        ScopedAStatus runEchoReverseServer() override {
             auto result = start_echo_reverse_server();
             if (result.ok()) {
                 return ScopedAStatus::ok();
@@ -251,6 +278,41 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getFilePermissions(const std::string& path, int32_t* out) override {
+            struct stat sb;
+            if (stat(path.c_str(), &sb) != -1) {
+                *out = sb.st_mode;
+            } else {
+                std::string msg = "stat " + path + " failed :  " + std::strerror(errno);
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+            return ScopedAStatus::ok();
+        }
+
+        ScopedAStatus getMountFlags(const std::string& mount_point, int32_t* out) override {
+            Fstab fstab;
+            if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   "Failed to read /proc/mounts");
+            }
+            FstabEntry* entry = GetEntryForMountPoint(&fstab, mount_point);
+            if (entry == nullptr) {
+                std::string msg = mount_point + " not found in /proc/mounts";
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+            *out = entry->flags;
+            return ScopedAStatus::ok();
+        }
+
+        ScopedAStatus requestCallback(const std::shared_ptr<IAppCallback>& appCallback) {
+            auto vmCallback = ndk::SharedRefBase::make<VmCallbackImpl>(appCallback);
+            std::thread callback_thread{[=] { appCallback->setVmCallback(vmCallback); }};
+            callback_thread.detach();
+            return ScopedAStatus::ok();
+        }
+
         ScopedAStatus quit() override { exit(0); }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
index 278e1a2..edd6bf5 100644
--- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -29,6 +29,7 @@
 
 import com.android.microdroid.test.vmshare.IVmShareTestService;
 import com.android.microdroid.testservice.ITestService;
+import com.android.microdroid.testservice.IAppCallback;
 
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
@@ -230,6 +231,21 @@
         }
 
         @Override
+        public int getFilePermissions(String path) throws RemoteException {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public int getMountFlags(String path) throws RemoteException {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public void requestCallback(IAppCallback appCallback) {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
         public void quit() throws RemoteException {
             throw new UnsupportedOperationException("Not supported");
         }
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 1781007..48e2431 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -20,6 +20,7 @@
 use crate::composite::make_composite_image;
 use crate::crosvm::{CrosvmConfig, DiskFile, PayloadState, VmContext, VmInstance, VmState};
 use crate::debug_config::should_prepare_console_output;
+use crate::debug_config::is_ramdump_needed;
 use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images};
 use crate::selinux::{getfilecon, SeContext};
 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
@@ -318,6 +319,17 @@
             check_gdb_allowed(config)?;
         }
 
+        let ramdump = if is_ramdump_needed(config) {
+            Some(prepare_ramdump_file(&temporary_directory)?)
+        } else {
+            None
+        };
+
+        let debug_level = match config {
+            VirtualMachineConfig::AppConfig(app_config) => app_config.debugLevel,
+            _ => DebugLevel::NONE,
+        };
+
         let state = &mut *self.state.lock().unwrap();
         let console_fd =
             clone_or_prepare_logger_fd(config, console_fd, format!("Console({})", cid))?;
@@ -408,19 +420,6 @@
             }
         };
 
-        // Creating this ramdump file unconditionally is not harmful as ramdump will be created
-        // only when the VM is configured as such. `ramdump_write` is sent to crosvm and will
-        // be the backing store for the /dev/hvc1 where VM will emit ramdump to. `ramdump_read`
-        // will be sent back to the client (i.e. the VM owner) for readout.
-        let ramdump_path = temporary_directory.join("ramdump");
-        let ramdump = prepare_ramdump_file(&ramdump_path).map_err(|e| {
-            error!("Failed to prepare ramdump file: {:?}", e);
-            Status::new_service_specific_error_str(
-                -1,
-                Some(format!("Failed to prepare ramdump file: {:?}", e)),
-            )
-        })?;
-
         // Actually start the VM.
         let crosvm_config = CrosvmConfig {
             cid,
@@ -431,13 +430,14 @@
             disks,
             params: config.params.to_owned(),
             protected: *is_protected,
+            debug_level,
             memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
             cpus,
             host_cpu_topology,
             task_profiles: config.taskProfiles.clone(),
             console_fd,
             log_fd,
-            ramdump: Some(ramdump),
+            ramdump,
             indirect_files,
             platform_version: parse_platform_version_req(&config.platformVersion)?,
             detect_hangup: is_app_config,
@@ -486,10 +486,6 @@
     part.flush()
 }
 
-fn prepare_ramdump_file(ramdump_path: &Path) -> Result<File> {
-    File::create(ramdump_path).context(format!("Failed to create ramdump file {:?}", &ramdump_path))
-}
-
 fn round_up(input: u64, granularity: u64) -> u64 {
     if granularity == 0 {
         return input;
@@ -979,6 +975,22 @@
     })
 }
 
+/// Create the empty ramdump file
+fn prepare_ramdump_file(temporary_directory: &Path) -> binder::Result<File> {
+    // `ramdump_write` is sent to crosvm and will be the backing store for the /dev/hvc1 where
+    // VM will emit ramdump to. `ramdump_read` will be sent back to the client (i.e. the VM
+    // owner) for readout.
+    let ramdump_path = temporary_directory.join("ramdump");
+    let ramdump = File::create(ramdump_path).map_err(|e| {
+        error!("Failed to prepare ramdump file: {:?}", e);
+        Status::new_service_specific_error_str(
+            -1,
+            Some(format!("Failed to prepare ramdump file: {:?}", e)),
+        )
+    })?;
+    Ok(ramdump)
+}
+
 fn is_protected(config: &VirtualMachineConfig) -> bool {
     match config {
         VirtualMachineConfig::RawConfig(config) => config.protectedVm,
@@ -1025,13 +1037,12 @@
         return Ok(Some(clone_file(fd)?));
     }
 
-    if let VirtualMachineConfig::AppConfig(app_config) = config {
-        if !should_prepare_console_output(app_config.debugLevel) {
-            return Ok(None);
-        }
-    } else {
+    let VirtualMachineConfig::AppConfig(app_config) = config else {
         return Ok(None);
-    }
+    };
+    if !should_prepare_console_output(app_config.debugLevel) {
+        return Ok(None);
+    };
 
     let (raw_read_fd, raw_write_fd) = pipe().map_err(|e| {
         Status::new_service_specific_error_str(-1, Some(format!("Failed to create pipe: {:?}", e)))
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 1456d17..9db0971 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -16,6 +16,7 @@
 
 use crate::aidl::{remove_temporary_files, Cid, VirtualMachineCallbacks};
 use crate::atom::{get_num_cpus, write_vm_exited_stats};
+use crate::debug_config::should_prepare_console_output;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use command_fds::CommandFdExt;
 use lazy_static::lazy_static;
@@ -41,7 +42,10 @@
 use std::time::{Duration, SystemTime};
 use std::thread::{self, JoinHandle};
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
-use android_system_virtualizationservice::aidl::android::system::virtualizationservice::MemoryTrimLevel::MemoryTrimLevel;
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    MemoryTrimLevel::MemoryTrimLevel,
+    VirtualMachineAppConfig::DebugLevel::DebugLevel
+};
 use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::IGlobalVmContext::IGlobalVmContext;
 use binder::Strong;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
@@ -68,6 +72,8 @@
 const CROSVM_CRASH_STATUS: i32 = 33;
 /// The exit status which crosvm returns when vcpu is stalled.
 const CROSVM_WATCHDOG_REBOOT_STATUS: i32 = 36;
+/// The size of memory (in MiB) reserved for ramdump
+const RAMDUMP_RESERVED_MIB: u32 = 17;
 
 const MILLIS_PER_SEC: i64 = 1000;
 
@@ -95,6 +101,7 @@
     pub disks: Vec<DiskFile>,
     pub params: Option<String>,
     pub protected: bool,
+    pub debug_level: DebugLevel,
     pub memory_mib: Option<NonZeroU32>,
     pub cpus: Option<NonZeroU32>,
     pub host_cpu_topology: bool,
@@ -530,6 +537,9 @@
     /// Checks if ramdump has been created. If so, send it to tombstoned.
     fn handle_ramdump(&self) -> Result<(), Error> {
         let ramdump_path = self.temporary_directory.join("ramdump");
+        if !ramdump_path.as_path().try_exists()? {
+            return Ok(());
+        }
         if std::fs::metadata(&ramdump_path)?.len() > 0 {
             Self::send_ramdump_to_tombstoned(&ramdump_path)?;
         }
@@ -665,24 +675,6 @@
     }
 }
 
-fn should_configure_ramdump(protected: bool) -> bool {
-    if protected {
-        // Protected VM needs ramdump configuration here.
-        // pvmfw will disable ramdump if unnecessary.
-        true
-    } else {
-        // For unprotected VM, ramdump should be handled here.
-        // ramdump wouldn't be enabled if ramdump is explicitly set to <1>.
-        if let Ok(mut file) = File::open("/proc/device-tree/avf/guest/common/ramdump") {
-            let mut ramdump: [u8; 4] = Default::default();
-            file.read_exact(&mut ramdump).map_err(|_| false).unwrap();
-            // DT spec uses big endian although Android is always little endian.
-            return u32::from_be_bytes(ramdump) == 1;
-        }
-        false
-    }
-}
-
 /// Starts an instance of `crosvm` to manage a new VM.
 fn run_vm(
     config: CrosvmConfig,
@@ -722,12 +714,32 @@
         let virtio_pci_device_count = 4 + config.disks.len();
         // crosvm virtio queue has 256 entries, so 2 MiB per device (2 pages per entry) should be
         // enough.
-        let swiotlb_size_mib = 2 * virtio_pci_device_count;
+        let swiotlb_size_mib = 2 * virtio_pci_device_count as u32;
         command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
 
         // Workaround to keep crash_dump from trying to read protected guest memory.
         // Context in b/238324526.
         command.arg("--unmap-guest-memory-on-fork");
+
+        if config.ramdump.is_some() {
+            // Protected VM needs to reserve memory for ramdump here. pvmfw will drop This
+            // if ramdump should be disabled (via debug policy). Note that we reserve more
+            // memory for the restricted dma pool.
+            let ramdump_reserve = RAMDUMP_RESERVED_MIB + swiotlb_size_mib;
+            command.arg("--params").arg(format!("crashkernel={ramdump_reserve}M"));
+        }
+    } else {
+        if config.ramdump.is_some() {
+            command.arg("--params").arg(format!("crashkernel={RAMDUMP_RESERVED_MIB}M"));
+        }
+        if config.debug_level == DebugLevel::NONE
+            && should_prepare_console_output(config.debug_level)
+        {
+            // bootconfig.normal will be used, but we need log.
+            // pvmfw will add following commands by itself, but non-protected VM should do so here.
+            command.arg("--params").arg("printk.devkmsg=on");
+            command.arg("--params").arg("console=hvc0");
+        }
     }
 
     if let Some(memory_mib) = config.memory_mib {
@@ -816,10 +828,6 @@
     debug!("Preserving FDs {:?}", preserved_fds);
     command.preserved_fds(preserved_fds);
 
-    if should_configure_ramdump(config.protected) {
-        command.arg("--params").arg("crashkernel=17M");
-    }
-
     print_crosvm_args(&command);
 
     let result = SharedChild::spawn(&mut command)?;
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index 332df08..a4ec419 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -15,20 +15,18 @@
 //! Functions for AVF debug policy and debug level
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    VirtualMachineAppConfig::DebugLevel::DebugLevel
+    VirtualMachineAppConfig::DebugLevel::DebugLevel, VirtualMachineConfig::VirtualMachineConfig,
 };
 use std::fs::File;
 use std::io::Read;
 
 /// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
 fn get_debug_policy_bool(path: &'static str) -> Option<bool> {
-    if let Ok(mut file) = File::open(path) {
-        let mut log: [u8; 4] = Default::default();
-        file.read_exact(&mut log).map_err(|_| false).unwrap();
-        // DT spec uses big endian although Android is always little endian.
-        return Some(u32::from_be_bytes(log) == 1);
-    }
-    None
+    let mut file = File::open(path).ok()?;
+    let mut log: [u8; 4] = Default::default();
+    file.read_exact(&mut log).ok()?;
+    // DT spec uses big endian although Android is always little endian.
+    Some(u32::from_be_bytes(log) == 1)
 }
 
 /// Get whether console output should be configred for VM to leave console and adb log.
@@ -37,3 +35,24 @@
     debug_level != DebugLevel::NONE
         || get_debug_policy_bool("/proc/device-tree/avf/guest/common/log").unwrap_or_default()
 }
+
+/// Decision to support ramdump
+pub fn is_ramdump_needed(config: &VirtualMachineConfig) -> bool {
+    let enabled_in_dp =
+        get_debug_policy_bool("/proc/device-tree/avf/guest/common/ramdump").unwrap_or_default();
+    let (protected, debuggable) = match config {
+        VirtualMachineConfig::RawConfig(config) => {
+            // custom VMs are considered debuggable for flexibility
+            (config.protectedVm, true)
+        }
+        VirtualMachineConfig::AppConfig(config) => {
+            (config.protectedVm, config.debugLevel == DebugLevel::FULL)
+        }
+    };
+
+    if protected {
+        enabled_in_dp
+    } else {
+        enabled_in_dp || debuggable
+    }
+}
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 {
diff --git a/zipfuse/src/inode.rs b/zipfuse/src/inode.rs
index 3edbc49..ea63422 100644
--- a/zipfuse/src/inode.rs
+++ b/zipfuse/src/inode.rs
@@ -99,12 +99,8 @@
         InodeData { mode, size: 0, data: InodeDataData::Directory(HashMap::new()) }
     }
 
-    fn new_file(zip_index: ZipIndex, zip_file: &zip::read::ZipFile) -> InodeData {
-        InodeData {
-            mode: zip_file.unix_mode().unwrap_or(DEFAULT_FILE_MODE),
-            size: zip_file.size(),
-            data: InodeDataData::File(zip_index),
-        }
+    fn new_file(zip_index: ZipIndex, mode: u32, zip_file: &zip::read::ZipFile) -> InodeData {
+        InodeData { mode, size: zip_file.size(), data: InodeDataData::File(zip_index) }
     }
 
     fn add_to_directory(&mut self, name: CString, entry: DirectoryEntry) {
@@ -188,6 +184,16 @@
 
             let mut parent = ROOT;
             let mut iter = path.iter().peekable();
+
+            let mut file_mode = DEFAULT_FILE_MODE;
+            if path.starts_with("bin/") {
+                // Allow files under bin to have execute permission, this enables payloads to bundle
+                // additional binaries that they might want to execute.
+                // An example of such binary is measure_io one used in the authfs performance tests.
+                // More context available at b/265261525 and b/270955654.
+                file_mode |= libc::S_IXUSR;
+            }
+
             while let Some(name) = iter.next() {
                 // TODO(jiyong): remove this check by canonicalizing `path`
                 if name == ".." {
@@ -211,8 +217,11 @@
                 }
 
                 // No inode found. Create a new inode and add it to the inode table.
+                // At the moment of writing this comment the apk file doesn't specify any
+                // permissions (apart from the ones on lib/), but it might change in the future.
+                // TODO(b/270955654): should we control the file permissions ourselves?
                 let inode = if is_file {
-                    InodeData::new_file(i, &file)
+                    InodeData::new_file(i, file.unix_mode().unwrap_or(file_mode), &file)
                 } else if is_leaf {
                     InodeData::new_dir(file.unix_mode().unwrap_or(DEFAULT_DIR_MODE))
                 } else {