Merge "rpc_binder: Set CID of vsock servers"
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 9c8311d..7140ae2 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -137,7 +137,10 @@
 
 fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
     create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
-    let mount_options = CString::new("").unwrap();
+    let mount_options = CString::new(
+        "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
+    )
+    .unwrap();
     let source = CString::new(source.as_os_str().as_bytes())?;
     let mountpoint = CString::new(mountpoint.as_os_str().as_bytes())?;
     let fstype = CString::new("ext4").unwrap();
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index fb7c98c..c98c62e 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -58,10 +58,12 @@
   public final class VirtualMachineConfig {
     method @NonNull public String getApkPath();
     method @NonNull public int getDebugLevel();
+    method @IntRange(from=0) public long getEncryptedStorageKib();
     method @IntRange(from=0) public int getMemoryMib();
     method @IntRange(from=1) public int getNumCpus();
     method @Nullable public String getPayloadBinaryPath();
     method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
+    method public boolean isEncryptedStorageEnabled();
     method public boolean isProtectedVm();
     field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
     field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
@@ -72,6 +74,7 @@
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
+    method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageKib(@IntRange(from=0) long);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryMib(@IntRange(from=0) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryPath(@NonNull String);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index b8be703..504d3c7 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -169,6 +169,9 @@
     /** Size of the instance image. 10 MB. */
     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
 
+    /** Name of the file backing the encrypted storage */
+    private static final String ENCRYPTED_STORE_FILE = "storage.img";
+
     /** The package which owns this VM. */
     @NonNull private final String mPackageName;
 
@@ -191,6 +194,9 @@
     /** Path to the idsig file for this VM. */
     @NonNull private final File mIdsigFilePath;
 
+    /** File that backs the encrypted storage - Will be null if not enabled. */
+    @Nullable private final File mEncryptedStoreFilePath;
+
     /**
      * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
      * idsigs are to be generated.
@@ -324,6 +330,10 @@
         mExtraApks = setupExtraApks(context, config, thisVmDir);
         mMemoryManagementCallbacks = new MemoryManagementCallbacks();
         mContext = context;
+        mEncryptedStoreFilePath =
+                (config.isEncryptedStorageEnabled())
+                        ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
+                        : null;
     }
 
     /**
@@ -354,6 +364,16 @@
                 throw new VirtualMachineException("failed to create instance image", e);
             }
             vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
+
+            if (vmDescriptor.getEncryptedStoreFd() != null) {
+                try {
+                    vm.mEncryptedStoreFilePath.createNewFile();
+                } catch (IOException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage image", e);
+                }
+                vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
+            }
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -386,6 +406,14 @@
             } catch (IOException e) {
                 throw new VirtualMachineException("failed to create instance image", e);
             }
+            if (config.isEncryptedStorageEnabled()) {
+                try {
+                    vm.mEncryptedStoreFilePath.createNewFile();
+                } catch (IOException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage image", e);
+                }
+            }
 
             IVirtualizationService service =
                     IVirtualizationService.Stub.asInterface(
@@ -403,6 +431,22 @@
             } catch (ServiceSpecificException | IllegalArgumentException e) {
                 throw new VirtualMachineException("failed to create instance partition", e);
             }
+
+            if (config.isEncryptedStorageEnabled()) {
+                try {
+                    service.initializeWritablePartition(
+                            ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
+                            config.getEncryptedStorageKib() * 1024L,
+                            PartitionType.ENCRYPTEDSTORE);
+                } catch (FileNotFoundException e) {
+                    throw new VirtualMachineException("encrypted storage image missing", e);
+                } catch (RemoteException e) {
+                    throw e.rethrowAsRuntimeException();
+                } catch (ServiceSpecificException | IllegalArgumentException e) {
+                    throw new VirtualMachineException(
+                            "failed to create encrypted storage partition", e);
+                }
+            }
             return vm;
         } catch (VirtualMachineException | RuntimeException e) {
             // If anything goes wrong, delete any files created so far and the VM's directory
@@ -432,7 +476,9 @@
         if (!vm.mInstanceFilePath.exists()) {
             throw new VirtualMachineException("instance image missing");
         }
-
+        if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
+            throw new VirtualMachineException("Storage image missing");
+        }
         return vm;
     }
 
@@ -681,8 +727,12 @@
 
                 // Re-open idsig file in read-only mode
                 appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
-                appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath,
-                        MODE_READ_WRITE);
+                appConfig.instanceImage =
+                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+                if (mEncryptedStoreFilePath != null) {
+                    appConfig.encryptedStorageImage =
+                            ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+                }
                 List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
                 for (ExtraApkSpec extraApk : mExtraApks) {
                     extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
@@ -1017,7 +1067,10 @@
             try {
                 return new VirtualMachineDescriptor(
                         ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
-                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY));
+                        ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
+                        mEncryptedStoreFilePath != null
+                                ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
+                                : null);
             } catch (IOException e) {
                 throw new VirtualMachineException(e);
             }
@@ -1183,4 +1236,14 @@
             throw new VirtualMachineException("failed to transfer instance image", e);
         }
     }
+
+    private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
+            throws VirtualMachineException {
+        try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
+                FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
+            storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
+        } catch (IOException e) {
+            throw new VirtualMachineException("failed to transfer encryptedstore image", e);
+        }
+    }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index a9e062a..459b614 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -65,6 +65,7 @@
     private static final String KEY_PROTECTED_VM = "protectedVm";
     private static final String KEY_MEMORY_MIB = "memoryMib";
     private static final String KEY_NUM_CPUS = "numCpus";
+    private static final String KEY_ENCRYPTED_STORAGE_KIB = "encryptedStorageKib";
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -120,6 +121,9 @@
      */
     @Nullable private final String mPayloadBinaryPath;
 
+    /** The size of storage in KB. 0 indicates that encryptedStorage is not required */
+    private final long mEncryptedStorageKib;
+
     private VirtualMachineConfig(
             @NonNull String apkPath,
             @Nullable String payloadConfigPath,
@@ -127,7 +131,8 @@
             @DebugLevel int debugLevel,
             boolean protectedVm,
             int memoryMib,
-            int numCpus) {
+            int numCpus,
+            long encryptedStorageKib) {
         requireNonNull(apkPath);
         if (!apkPath.startsWith("/")) {
             throw new IllegalArgumentException("APK path must be an absolute path");
@@ -167,6 +172,9 @@
             throw new UnsupportedOperationException(
                     "Unprotected VMs are not supported on this device.");
         }
+        if (encryptedStorageKib < 0) {
+            throw new IllegalArgumentException("Encrypted Storage size cannot be negative");
+        }
 
         mApkPath = apkPath;
         mPayloadConfigPath = payloadConfigPath;
@@ -175,6 +183,7 @@
         mProtectedVm = protectedVm;
         mMemoryMib = memoryMib;
         mNumCpus = numCpus;
+        mEncryptedStorageKib = encryptedStorageKib;
     }
 
     /** Loads a config from a file. */
@@ -226,9 +235,17 @@
         boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
         int memoryMib = b.getInt(KEY_MEMORY_MIB);
         int numCpus = b.getInt(KEY_NUM_CPUS);
+        long encryptedStorageKib = b.getLong(KEY_ENCRYPTED_STORAGE_KIB);
 
-        return new VirtualMachineConfig(apkPath, payloadConfigPath, payloadBinaryPath, debugLevel,
-                protectedVm, memoryMib, numCpus);
+        return new VirtualMachineConfig(
+                apkPath,
+                payloadConfigPath,
+                payloadBinaryPath,
+                debugLevel,
+                protectedVm,
+                memoryMib,
+                numCpus,
+                encryptedStorageKib);
     }
 
     /** Persists this config to a file. */
@@ -253,6 +270,9 @@
         if (mMemoryMib > 0) {
             b.putInt(KEY_MEMORY_MIB, mMemoryMib);
         }
+        if (mEncryptedStorageKib > 0) {
+            b.putLong(KEY_ENCRYPTED_STORAGE_KIB, mEncryptedStorageKib);
+        }
         b.writeToStream(output);
     }
 
@@ -337,6 +357,28 @@
     }
 
     /**
+     * Returns whether encrypted storage is enabled or not.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isEncryptedStorageEnabled() {
+        return mEncryptedStorageKib > 0;
+    }
+
+    /**
+     * Returns the size of encrypted storage (in KB) available in the VM, or 0 if encrypted storage
+     * is not enabled
+     *
+     * @hide
+     */
+    @SystemApi
+    @IntRange(from = 0)
+    public long getEncryptedStorageKib() {
+        return mEncryptedStorageKib;
+    }
+
+    /**
      * Tests if this config is compatible with other config. Being compatible means that the configs
      * can be interchangeably used for the same virtual machine. Compatible changes includes the
      * number of CPUs and the size of the RAM. All other changes (e.g. using a different payload,
@@ -348,6 +390,7 @@
     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
         return this.mDebugLevel == other.mDebugLevel
                 && this.mProtectedVm == other.mProtectedVm
+                && this.mEncryptedStorageKib == other.mEncryptedStorageKib
                 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
                 && Objects.equals(this.mPayloadBinaryPath, other.mPayloadBinaryPath)
                 && this.mApkPath.equals(other.mApkPath);
@@ -405,6 +448,7 @@
         private boolean mProtectedVmSet;
         private int mMemoryMib;
         private int mNumCpus;
+        private long mEncryptedStorageKib;
 
         /**
          * Creates a builder for the given context.
@@ -416,6 +460,7 @@
             mContext = requireNonNull(context, "context must not be null");
             mDebugLevel = DEBUG_LEVEL_NONE;
             mNumCpus = 1;
+            mEncryptedStorageKib = 0;
         }
 
         /**
@@ -433,8 +478,14 @@
             }
 
             return new VirtualMachineConfig(
-                    apkPath, mPayloadConfigPath, mPayloadBinaryPath, mDebugLevel, mProtectedVm,
-                    mMemoryMib, mNumCpus);
+                    apkPath,
+                    mPayloadConfigPath,
+                    mPayloadBinaryPath,
+                    mDebugLevel,
+                    mProtectedVm,
+                    mMemoryMib,
+                    mNumCpus,
+                    mEncryptedStorageKib);
         }
 
         /**
@@ -530,5 +581,27 @@
             mNumCpus = num;
             return this;
         }
+
+        /**
+         * Sets the size (in KB) of encrypted storage available to the VM.
+         *
+         * <p>The storage is encrypted with a key deterministically derived from the VM identity
+         *
+         * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
+         * backing file (containing encrypted data) is stored in the app's private data directory.
+         *
+         * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
+         * the encrypted data is modified.
+         *
+         * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public Builder setEncryptedStorageKib(@IntRange(from = 0) long encryptedStorageKib) {
+            mEncryptedStorageKib = encryptedStorageKib;
+            return this;
+        }
     }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
index edaf5b4..c9718aa 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineDescriptor.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -37,7 +38,9 @@
 public final class VirtualMachineDescriptor implements Parcelable {
     @NonNull private final ParcelFileDescriptor mConfigFd;
     @NonNull private final ParcelFileDescriptor mInstanceImgFd;
-    // TODO(b/243129654): Add trusted storage fd once it is available.
+    // File descriptor of the image backing the encrypted storage - Will be null if encrypted
+    // storage is not enabled. */
+    @Nullable private final ParcelFileDescriptor mEncryptedStoreFd;
 
     @Override
     public int describeContents() {
@@ -48,6 +51,7 @@
     public void writeToParcel(@NonNull Parcel out, int flags) {
         mConfigFd.writeToParcel(out, flags);
         mInstanceImgFd.writeToParcel(out, flags);
+        if (mEncryptedStoreFd != null) mEncryptedStoreFd.writeToParcel(out, flags);
     }
 
     @NonNull
@@ -78,14 +82,27 @@
         return mInstanceImgFd;
     }
 
+    /**
+     * @return File descriptor of image backing the encrypted storage.
+     *     <p>This method will return null if encrypted storage is not enabled.
+     */
+    @Nullable
+    ParcelFileDescriptor getEncryptedStoreFd() {
+        return mEncryptedStoreFd;
+    }
+
     VirtualMachineDescriptor(
-            @NonNull ParcelFileDescriptor configFd, @NonNull ParcelFileDescriptor instanceImgFd) {
+            @NonNull ParcelFileDescriptor configFd,
+            @NonNull ParcelFileDescriptor instanceImgFd,
+            @Nullable ParcelFileDescriptor encryptedStoreFd) {
         mConfigFd = configFd;
         mInstanceImgFd = instanceImgFd;
+        mEncryptedStoreFd = encryptedStoreFd;
     }
 
     private VirtualMachineDescriptor(Parcel in) {
         mConfigFd = requireNonNull(in.readFileDescriptor());
         mInstanceImgFd = requireNonNull(in.readFileDescriptor());
+        mEncryptedStoreFd = in.readFileDescriptor();
     }
 }
diff --git a/libs/avb/Android.bp b/libs/avb/Android.bp
index 28e969d..a19a538 100644
--- a/libs/avb/Android.bp
+++ b/libs/avb/Android.bp
@@ -39,7 +39,7 @@
 
 rust_library_rlib {
     name: "libavb_nostd",
-    crate_name: "avb_nostd",
+    crate_name: "avb",
     srcs: ["src/lib.rs"],
     no_stdlibs: true,
     prefer_rlib: true,
diff --git a/libs/avb/src/lib.rs b/libs/avb/src/lib.rs
index 81b554d..21b7d2a 100644
--- a/libs/avb/src/lib.rs
+++ b/libs/avb/src/lib.rs
@@ -16,6 +16,8 @@
 
 #![no_std]
 
-mod avb_ops;
+extern crate alloc;
 
-pub use avb_ops::{verify_image, AvbImageVerifyError};
+mod ops;
+
+pub use ops::{verify_image, AvbImageVerifyError};
diff --git a/libs/avb/src/avb_ops.rs b/libs/avb/src/ops.rs
similarity index 99%
rename from libs/avb/src/avb_ops.rs
rename to libs/avb/src/ops.rs
index 900e152..429c980 100644
--- a/libs/avb/src/avb_ops.rs
+++ b/libs/avb/src/ops.rs
@@ -19,8 +19,6 @@
 #![allow(dead_code)]
 #![allow(unused_imports)]
 
-extern crate alloc;
-
 use alloc::ffi::CString;
 use avb_bindgen::{
     avb_slot_verify, AvbHashtreeErrorMode_AVB_HASHTREE_ERROR_MODE_EIO,
diff --git a/libs/vbmeta/src/lib.rs b/libs/vbmeta/src/lib.rs
index 8e81ea4..691b65c 100644
--- a/libs/vbmeta/src/lib.rs
+++ b/libs/vbmeta/src/lib.rs
@@ -28,11 +28,10 @@
 };
 use std::fs::File;
 use std::io::{self, Read, Seek, SeekFrom};
-use std::mem::{size_of, MaybeUninit};
+use std::mem::{size_of, transmute, MaybeUninit};
 use std::os::raw::c_uint;
 use std::path::Path;
 use std::ptr::null_mut;
-use std::slice;
 use thiserror::Error;
 
 pub use crate::descriptor::{Descriptor, Descriptors};
@@ -189,19 +188,10 @@
 /// Read the AVB footer, if present, given a reader that's positioned at the end of the image.
 fn read_avb_footer<R: Read + Seek>(image: &mut R) -> io::Result<Option<AvbFooter>> {
     image.seek(SeekFrom::Current(-(size_of::<AvbFooter>() as i64)))?;
+    let mut raw_footer = [0u8; size_of::<AvbFooter>()];
+    image.read_exact(&mut raw_footer)?;
     // SAFETY: the slice is the same size as the struct which only contains simple data types.
-    let mut footer = unsafe {
-        let mut footer = MaybeUninit::<AvbFooter>::uninit();
-        let footer_slice =
-            slice::from_raw_parts_mut(&mut footer as *mut _ as *mut u8, size_of::<AvbFooter>());
-        image.read_exact(footer_slice)?;
-        footer.assume_init()
-    };
-    // Check the magic matches "AVBf" to suppress misleading logs from libavb.
-    const AVB_FOOTER_MAGIC: [u8; 4] = [0x41, 0x56, 0x42, 0x66];
-    if footer.magic != AVB_FOOTER_MAGIC {
-        return Ok(None);
-    }
+    let mut footer = unsafe { transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(raw_footer) };
     // SAFETY: the function updates the struct in-place.
     if unsafe { avb_footer_validate_and_byteswap(&footer, &mut footer) } {
         Ok(Some(footer))
@@ -220,7 +210,7 @@
     use tempfile::TempDir;
 
     #[test]
-    fn test_unsigned_image() -> Result<()> {
+    fn unsigned_image_does_not_have_public_key() -> Result<()> {
         let test_dir = TempDir::new().unwrap();
         let test_file = test_dir.path().join("test.img");
         let mut cmd = Command::new("./avbtool");
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 028ac1f..2b8e03f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -71,7 +71,6 @@
         "atrace",
         "debuggerd",
         "linker",
-        "linkerconfig",
         "tombstoned.microdroid",
         "tombstone_transmit.microdroid",
         "cgroups.json",
@@ -83,7 +82,7 @@
         "microdroid_manifest",
         "microdroid_plat_sepolicy_and_mapping.sha256",
         "microdroid_property_contexts",
-        "mke2fs",
+        "mke2fs.microdroid",
 
         // TODO(b/195425111) these should be added automatically
         "libcrypto", // used by many (init_second_stage, microdroid_manager, toybox, etc)
diff --git a/microdroid/init.rc b/microdroid/init.rc
index a48ba4b..7402481 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,6 +17,10 @@
 
     start ueventd
 
+    # Generate empty linker config to suppress warnings
+    write /linkerconfig/ld.config.txt \#
+    chmod 644 /linkerconfig/ld.config.txt
+
 # If VM is debuggable, send logs to outside ot the VM via the serial console.
 # If non-debuggable, logs are internally consumed at /dev/null
 on early-init && property:ro.boot.microdroid.debuggable=1
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 3c490f4..6a37b88 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -77,7 +77,6 @@
 
 const APEX_CONFIG_DONE_PROP: &str = "apex_config.done";
 const DEBUGGABLE_PROP: &str = "ro.boot.microdroid.debuggable";
-const APK_MOUNT_DONE_PROP: &str = "microdroid_manager.apk.mounted";
 
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
@@ -383,15 +382,16 @@
         None
     };
 
+    let mut zipfuse = Zipfuse::default();
+
     // Before reading a file from the APK, start zipfuse
-    run_zipfuse(
+    zipfuse.mount(
         MountForExec::Allowed,
         "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0",
         Path::new("/dev/block/mapper/microdroid-apk"),
         Path::new(VM_APK_CONTENTS_PATH),
-        Some(APK_MOUNT_DONE_PROP),
-    )
-    .context("Failed to run zipfuse")?;
+        "microdroid_manager.apk.mounted".to_owned(),
+    )?;
 
     // Restricted APIs are only allowed to be used by platform or test components. Infer this from
     // the use of a VM config file since those can only be used by platform and test components.
@@ -414,7 +414,7 @@
             verified_data.extra_apks_data.len()
         ));
     }
-    mount_extra_apks(&config)?;
+    mount_extra_apks(&config, &mut zipfuse)?;
 
     // Wait until apex config is done. (e.g. linker configuration for apexes)
     wait_for_apex_config_done()?;
@@ -428,8 +428,8 @@
         control_service("stop", "tombstoned")?;
     }
 
-    // Wait until zipfuse has mounted the APK so we can access the payload
-    wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
+    // Wait until zipfuse has mounted the APKs so we can access the payload
+    zipfuse.wait_until_done()?;
 
     register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
 
@@ -480,21 +480,40 @@
     Disallowed,
 }
 
-fn run_zipfuse(
-    noexec: MountForExec,
-    option: &str,
-    zip_path: &Path,
-    mount_dir: &Path,
-    ready_prop: Option<&str>,
-) -> Result<Child> {
-    let mut cmd = Command::new(ZIPFUSE_BIN);
-    if let MountForExec::Disallowed = noexec {
-        cmd.arg("--noexec");
+#[derive(Default)]
+struct Zipfuse {
+    ready_properties: Vec<String>,
+}
+
+impl Zipfuse {
+    fn mount(
+        &mut self,
+        noexec: MountForExec,
+        option: &str,
+        zip_path: &Path,
+        mount_dir: &Path,
+        ready_prop: String,
+    ) -> Result<Child> {
+        let mut cmd = Command::new(ZIPFUSE_BIN);
+        if let MountForExec::Disallowed = noexec {
+            cmd.arg("--noexec");
+        }
+        cmd.args(["-p", &ready_prop, "-o", option]);
+        cmd.arg(zip_path).arg(mount_dir);
+        self.ready_properties.push(ready_prop);
+        cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
     }
-    if let Some(property_name) = ready_prop {
-        cmd.args(["-p", property_name]);
+
+    fn wait_until_done(self) -> Result<()> {
+        // We check the last-started check first in the hope that by the time it is done
+        // all or most of the others will also be done, minimising the number of times we
+        // block on a property.
+        for property in self.ready_properties.into_iter().rev() {
+            wait_for_property_true(&property)
+                .with_context(|| format!("Failed waiting for {property}"))?;
+        }
+        Ok(())
     }
-    cmd.arg("-o").arg(option).arg(zip_path).arg(mount_dir).spawn().context("Spawn zipfuse")
 }
 
 fn write_apex_payload_data(
@@ -664,21 +683,20 @@
     })
 }
 
-fn mount_extra_apks(config: &VmPayloadConfig) -> Result<()> {
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
     // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
     for i in 0..config.extra_apks.len() {
-        let mount_dir = format!("/mnt/extra-apk/{}", i);
+        let mount_dir = format!("/mnt/extra-apk/{i}");
         create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
 
         // don't wait, just detach
-        run_zipfuse(
+        zipfuse.mount(
             MountForExec::Disallowed,
             "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
-            Path::new(&format!("/dev/block/mapper/extra-apk-{}", i)),
+            Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
             Path::new(&mount_dir),
-            None,
-        )
-        .context("Failed to zipfuse extra apks")?;
+            format!("microdroid_manager.extra_apk.mounted.{i}"),
+        )?;
     }
 
     Ok(())
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index b6c115b..4218fae 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -20,6 +20,7 @@
         "liblog_rust_nostd",
         "libpvmfw_embedded_key",
         "libtinyvec_nostd",
+        "libvirtio_drivers",
         "libvmbase",
     ],
     apex_available: ["com.android.virt"],
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index bffc140..2763e80 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -48,6 +48,8 @@
     InvalidRamdisk,
     /// Failed to verify the payload.
     PayloadVerificationError,
+    /// Error interacting with a VirtIO PCI device.
+    PciError,
 }
 
 main!(start);
@@ -330,6 +332,12 @@
     slice::from_raw_parts_mut(base as *mut u8, size)
 }
 
+enum AppendedConfigType {
+    Valid,
+    Invalid,
+    NotFound,
+}
+
 enum AppendedPayload<'a> {
     /// Configuration data.
     Config(config::Config<'a>),
@@ -340,24 +348,32 @@
 impl<'a> AppendedPayload<'a> {
     /// SAFETY - 'data' should respect the alignment of config::Header.
     unsafe fn new(data: &'a mut [u8]) -> Option<Self> {
-        if Self::is_valid_config(data) {
-            Some(Self::Config(config::Config::new(data).unwrap()))
-        } else if cfg!(feature = "legacy") {
-            const BCC_SIZE: usize = helpers::SIZE_4KB;
-            warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
-            Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
-        } else {
-            None
+        match Self::guess_config_type(data) {
+            AppendedConfigType::Valid => Some(Self::Config(config::Config::new(data).unwrap())),
+            AppendedConfigType::NotFound if cfg!(feature = "legacy") => {
+                const BCC_SIZE: usize = helpers::SIZE_4KB;
+                warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
+                Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
+            }
+            _ => None,
         }
     }
 
-    unsafe fn is_valid_config(data: &mut [u8]) -> bool {
+    unsafe fn guess_config_type(data: &mut [u8]) -> AppendedConfigType {
         // This function is necessary to prevent the borrow checker from getting confused
         // about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
         let addr = data.as_ptr();
-        config::Config::new(data)
-            .map_err(|e| warn!("Invalid configuration data at {addr:?}: {e}"))
-            .is_ok()
+        match config::Config::new(data) {
+            Err(config::Error::InvalidMagic) => {
+                warn!("No configuration data found at {addr:?}");
+                AppendedConfigType::NotFound
+            }
+            Err(e) => {
+                error!("Invalid configuration data at {addr:?}: {e}");
+                AppendedConfigType::Invalid
+            }
+            Ok(_) => AppendedConfigType::Valid,
+        }
     }
 
     #[allow(dead_code)] // TODO(b/232900974)
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 7222a0d..79b6f57 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -32,9 +32,13 @@
 mod pci;
 mod smccc;
 
-use crate::{entry::RebootReason, memory::MemoryTracker, pci::PciInfo};
-use avb::PUBLIC_KEY;
-use avb_nostd::verify_image;
+use crate::{
+    avb::PUBLIC_KEY,
+    entry::RebootReason,
+    memory::MemoryTracker,
+    pci::{allocate_all_virtio_bars, PciError, PciInfo, PciMemory32Allocator},
+};
+use ::avb::verify_image;
 use dice::bcc;
 use libfdt::Fdt;
 use log::{debug, error, info, trace};
@@ -57,9 +61,15 @@
     trace!("BCC: {bcc:x?}");
 
     // Set up PCI bus for VirtIO devices.
-    let pci_info = PciInfo::from_fdt(fdt)?;
-    info!("PCI: {:#x?}", pci_info);
+    let pci_info = PciInfo::from_fdt(fdt).map_err(handle_pci_error)?;
+    debug!("PCI: {:#x?}", pci_info);
     pci_info.map(memory)?;
+    let mut bar_allocator = PciMemory32Allocator::new(&pci_info);
+    debug!("Allocator: {:#x?}", bar_allocator);
+    // Safety: This is the only place where we call make_pci_root, and this main function is only
+    // called once.
+    let mut pci_root = unsafe { pci_info.make_pci_root() };
+    allocate_all_virtio_bars(&mut pci_root, &mut bar_allocator).map_err(handle_pci_error)?;
 
     verify_image(signed_kernel, PUBLIC_KEY).map_err(|e| {
         error!("Failed to verify the payload: {e}");
@@ -68,3 +78,24 @@
     info!("Starting payload...");
     Ok(())
 }
+
+/// Logs the given PCI error and returns the appropriate `RebootReason`.
+fn handle_pci_error(e: PciError) -> RebootReason {
+    error!("{}", e);
+    match e {
+        PciError::FdtErrorPci(_)
+        | PciError::FdtNoPci
+        | PciError::FdtErrorReg(_)
+        | PciError::FdtMissingReg
+        | PciError::FdtRegEmpty
+        | PciError::FdtRegMissingSize
+        | PciError::CamWrongSize(_)
+        | PciError::FdtErrorRanges(_)
+        | PciError::FdtMissingRanges
+        | PciError::RangeAddressMismatch { .. }
+        | PciError::NoSuitableRange => RebootReason::InvalidFdt,
+        PciError::BarInfoFailed(_)
+        | PciError::BarAllocationFailed { .. }
+        | PciError::UnsupportedBarType(_) => RebootReason::PciError,
+    }
+}
diff --git a/pvmfw/src/pci.rs b/pvmfw/src/pci.rs
index 3e6915a..d971c7b 100644
--- a/pvmfw/src/pci.rs
+++ b/pvmfw/src/pci.rs
@@ -18,13 +18,78 @@
     entry::RebootReason,
     memory::{MemoryRange, MemoryTracker},
 };
-use core::{ffi::CStr, ops::Range};
-use libfdt::{AddressRange, Fdt, FdtNode};
+use core::{
+    ffi::CStr,
+    fmt::{self, Display, Formatter},
+    ops::Range,
+};
+use libfdt::{AddressRange, Fdt, FdtError, FdtNode};
 use log::{debug, error};
+use virtio_drivers::pci::{
+    bus::{self, BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
+    virtio_device_type,
+};
 
 /// PCI MMIO configuration region size.
 const PCI_CFG_SIZE: usize = 0x100_0000;
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum PciError {
+    FdtErrorPci(FdtError),
+    FdtNoPci,
+    FdtErrorReg(FdtError),
+    FdtMissingReg,
+    FdtRegEmpty,
+    FdtRegMissingSize,
+    CamWrongSize(usize),
+    FdtErrorRanges(FdtError),
+    FdtMissingRanges,
+    RangeAddressMismatch { bus_address: u64, cpu_physical: u64 },
+    NoSuitableRange,
+    BarInfoFailed(bus::PciError),
+    BarAllocationFailed { size: u32, device_function: DeviceFunction },
+    UnsupportedBarType(MemoryBarType),
+}
+
+impl Display for PciError {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        match self {
+            Self::FdtErrorPci(e) => write!(f, "Error getting PCI node from FDT: {}", e),
+            Self::FdtNoPci => write!(f, "Failed to find PCI bus in FDT."),
+            Self::FdtErrorReg(e) => write!(f, "Error getting reg property from PCI node: {}", e),
+            Self::FdtMissingReg => write!(f, "PCI node missing reg property."),
+            Self::FdtRegEmpty => write!(f, "Empty reg property on PCI node."),
+            Self::FdtRegMissingSize => write!(f, "PCI reg property missing size."),
+            Self::CamWrongSize(cam_size) => write!(
+                f,
+                "FDT says PCI CAM is {} bytes but we expected {}.",
+                cam_size, PCI_CFG_SIZE
+            ),
+            Self::FdtErrorRanges(e) => {
+                write!(f, "Error getting ranges property from PCI node: {}", e)
+            }
+            Self::FdtMissingRanges => write!(f, "PCI node missing ranges property."),
+            Self::RangeAddressMismatch { bus_address, cpu_physical } => {
+                write!(
+                    f,
+                    "bus address {:#018x} != CPU physical address {:#018x}",
+                    bus_address, cpu_physical
+                )
+            }
+            Self::NoSuitableRange => write!(f, "No suitable PCI memory range found."),
+            Self::BarInfoFailed(e) => write!(f, "Error getting PCI BAR information: {}", e),
+            Self::BarAllocationFailed { size, device_function } => write!(
+                f,
+                "Failed to allocate memory BAR of size {} for PCI device {}.",
+                size, device_function
+            ),
+            Self::UnsupportedBarType(address_type) => {
+                write!(f, "Memory BAR address type {:?} not supported.", address_type)
+            }
+        }
+    }
+}
+
 /// Information about the PCI bus parsed from the device tree.
 #[derive(Debug)]
 pub struct PciInfo {
@@ -36,7 +101,7 @@
 
 impl PciInfo {
     /// Finds the PCI node in the FDT, parses its properties and validates it.
-    pub fn from_fdt(fdt: &Fdt) -> Result<Self, RebootReason> {
+    pub fn from_fdt(fdt: &Fdt) -> Result<Self, PciError> {
         let pci_node = pci_node(fdt)?;
 
         let cam_range = parse_cam_range(&pci_node)?;
@@ -61,48 +126,44 @@
 
         Ok(())
     }
+
+    /// Returns the `PciRoot` for the memory-mapped CAM found in the FDT. The CAM should be mapped
+    /// before this is called, by calling [`PciInfo::map`].
+    ///
+    /// # Safety
+    ///
+    /// To prevent concurrent access, only one `PciRoot` should exist in the program. Thus this
+    /// method must only be called once, and there must be no other `PciRoot` constructed using the
+    /// same CAM.
+    pub unsafe fn make_pci_root(&self) -> PciRoot {
+        PciRoot::new(self.cam_range.start as *mut u8, Cam::MmioCam)
+    }
 }
 
 /// Finds an FDT node with compatible=pci-host-cam-generic.
-fn pci_node(fdt: &Fdt) -> Result<FdtNode, RebootReason> {
+fn pci_node(fdt: &Fdt) -> Result<FdtNode, PciError> {
     fdt.compatible_nodes(CStr::from_bytes_with_nul(b"pci-host-cam-generic\0").unwrap())
-        .map_err(|e| {
-            error!("Failed to find PCI bus in FDT: {}", e);
-            RebootReason::InvalidFdt
-        })?
+        .map_err(PciError::FdtErrorPci)?
         .next()
-        .ok_or(RebootReason::InvalidFdt)
+        .ok_or(PciError::FdtNoPci)
 }
 
 /// Parses the "reg" property of the given PCI FDT node to find the MMIO CAM range.
-fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, RebootReason> {
+fn parse_cam_range(pci_node: &FdtNode) -> Result<MemoryRange, PciError> {
     let pci_reg = pci_node
         .reg()
-        .map_err(|e| {
-            error!("Error getting reg property from PCI node: {}", e);
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("PCI node missing reg property.");
-            RebootReason::InvalidFdt
-        })?
+        .map_err(PciError::FdtErrorReg)?
+        .ok_or(PciError::FdtMissingReg)?
         .next()
-        .ok_or_else(|| {
-            error!("Empty reg property on PCI node.");
-            RebootReason::InvalidFdt
-        })?;
+        .ok_or(PciError::FdtRegEmpty)?;
     let cam_addr = pci_reg.addr as usize;
-    let cam_size = pci_reg.size.ok_or_else(|| {
-        error!("PCI reg property missing size.");
-        RebootReason::InvalidFdt
-    })? as usize;
+    let cam_size = pci_reg.size.ok_or(PciError::FdtRegMissingSize)? as usize;
     debug!("Found PCI CAM at {:#x}-{:#x}", cam_addr, cam_addr + cam_size);
     // Check that the CAM is the size we expect, so we don't later try accessing it beyond its
     // bounds. If it is a different size then something is very wrong and we shouldn't continue to
     // access it; maybe there is some new version of PCI we don't know about.
     if cam_size != PCI_CFG_SIZE {
-        error!("FDT says PCI CAM is {} bytes but we expected {}.", cam_size, PCI_CFG_SIZE);
-        return Err(RebootReason::InvalidFdt);
+        return Err(PciError::CamWrongSize(cam_size));
     }
 
     Ok(cam_addr..cam_addr + cam_size)
@@ -110,20 +171,14 @@
 
 /// Parses the "ranges" property of the given PCI FDT node, and returns the largest suitable range
 /// to use for non-prefetchable 32-bit memory BARs.
-fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, RebootReason> {
+fn parse_ranges(pci_node: &FdtNode) -> Result<Range<u32>, PciError> {
     let mut memory_address = 0;
     let mut memory_size = 0;
 
     for AddressRange { addr: (flags, bus_address), parent_addr: cpu_physical, size } in pci_node
         .ranges::<(u32, u64), u64, u64>()
-        .map_err(|e| {
-            error!("Error getting ranges property from PCI node: {}", e);
-            RebootReason::InvalidFdt
-        })?
-        .ok_or_else(|| {
-            error!("PCI node missing ranges property.");
-            RebootReason::InvalidFdt
-        })?
+        .map_err(PciError::FdtErrorRanges)?
+        .ok_or(PciError::FdtMissingRanges)?
     {
         let flags = PciMemoryFlags(flags);
         let prefetchable = flags.prefetchable();
@@ -145,11 +200,7 @@
             && bus_address + size < u32::MAX.into()
         {
             if bus_address != cpu_physical {
-                error!(
-                    "bus address {:#018x} != CPU physical address {:#018x}",
-                    bus_address, cpu_physical
-                );
-                return Err(RebootReason::InvalidFdt);
+                return Err(PciError::RangeAddressMismatch { bus_address, cpu_physical });
             }
             memory_address = u32::try_from(cpu_physical).unwrap();
             memory_size = u32::try_from(size).unwrap();
@@ -157,8 +208,7 @@
     }
 
     if memory_size == 0 {
-        error!("No suitable PCI memory range found.");
-        return Err(RebootReason::InvalidFdt);
+        return Err(PciError::NoSuitableRange);
     }
 
     Ok(memory_address..memory_address + memory_size)
@@ -196,3 +246,101 @@
         }
     }
 }
+
+/// Allocates BARs for all VirtIO PCI devices.
+pub fn allocate_all_virtio_bars(
+    pci_root: &mut PciRoot,
+    allocator: &mut PciMemory32Allocator,
+) -> Result<(), PciError> {
+    for (device_function, info) in pci_root.enumerate_bus(0) {
+        let (status, command) = pci_root.get_status_command(device_function);
+        debug!(
+            "Found PCI device {} at {}, status {:?} command {:?}",
+            info, device_function, status, command
+        );
+        if let Some(virtio_type) = virtio_device_type(&info) {
+            debug!("  VirtIO {:?}", virtio_type);
+            allocate_bars(pci_root, device_function, allocator)?;
+        }
+    }
+
+    Ok(())
+}
+
+/// Allocates 32-bit memory addresses for PCI BARs.
+#[derive(Debug)]
+pub struct PciMemory32Allocator {
+    /// The start of the available (not yet allocated) address space for PCI BARs.
+    start: u32,
+    /// The end of the available address space.
+    end: u32,
+}
+
+impl PciMemory32Allocator {
+    pub fn new(pci_info: &PciInfo) -> Self {
+        Self { start: pci_info.bar_range.start, end: pci_info.bar_range.end }
+    }
+
+    /// Allocates a 32-bit memory address region for a PCI BAR of the given power-of-2 size.
+    ///
+    /// It will have alignment matching the size. The size must be a power of 2.
+    pub fn allocate_memory_32(&mut self, size: u32) -> Option<u32> {
+        assert!(size.is_power_of_two());
+        let allocated_address = align_up(self.start, size);
+        if allocated_address + size <= self.end {
+            self.start = allocated_address + size;
+            Some(allocated_address)
+        } else {
+            None
+        }
+    }
+}
+
+/// Allocates appropriately-sized memory regions and assigns them to the device's BARs.
+fn allocate_bars(
+    root: &mut PciRoot,
+    device_function: DeviceFunction,
+    allocator: &mut PciMemory32Allocator,
+) -> Result<(), PciError> {
+    let mut bar_index = 0;
+    while bar_index < 6 {
+        let info = root.bar_info(device_function, bar_index).map_err(PciError::BarInfoFailed)?;
+        debug!("BAR {}: {}", bar_index, info);
+        // Ignore I/O bars, as they aren't required for the VirtIO driver.
+        if let BarInfo::Memory { address_type, size, .. } = info {
+            match address_type {
+                _ if size == 0 => {}
+                MemoryBarType::Width32 => {
+                    let address = allocator
+                        .allocate_memory_32(size)
+                        .ok_or(PciError::BarAllocationFailed { size, device_function })?;
+                    debug!("Allocated address {:#010x}", address);
+                    root.set_bar_32(device_function, bar_index, address);
+                }
+                _ => {
+                    return Err(PciError::UnsupportedBarType(address_type));
+                }
+            }
+        }
+
+        bar_index += 1;
+        if info.takes_two_entries() {
+            bar_index += 1;
+        }
+    }
+
+    // Enable the device to use its BARs.
+    root.set_command(
+        device_function,
+        Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
+    );
+    let (status, command) = root.get_status_command(device_function);
+    debug!("Allocated BARs and enabled device, status {:?} command {:?}", status, command);
+
+    Ok(())
+}
+
+// TODO: Make the alignment functions in the helpers module generic once const_trait_impl is stable.
+const fn align_up(value: u32, alignment: u32) -> u32 {
+    ((value - 1) | (alignment - 1)) + 1
+}
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 8b0d6d2..2cd4e51 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -264,8 +264,9 @@
     @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void vmConfigUnitTests() {
-        VirtualMachineConfig minimal =
-                newVmConfigBuilder().setPayloadBinaryPath("binary/path").build();
+
+        VirtualMachineConfig.Builder minimalBuilder = newVmConfigBuilder();
+        VirtualMachineConfig minimal = minimalBuilder.setPayloadBinaryPath("binary/path").build();
 
         assertThat(minimal.getApkPath()).isEqualTo(getContext().getPackageCodePath());
         assertThat(minimal.getDebugLevel()).isEqualTo(DEBUG_LEVEL_NONE);
@@ -274,6 +275,8 @@
         assertThat(minimal.getPayloadBinaryPath()).isEqualTo("binary/path");
         assertThat(minimal.getPayloadConfigPath()).isNull();
         assertThat(minimal.isProtectedVm()).isEqualTo(isProtectedVm());
+        assertThat(minimal.isEncryptedStorageEnabled()).isFalse();
+        assertThat(minimal.getEncryptedStorageKib()).isEqualTo(0);
 
         int maxCpus = Runtime.getRuntime().availableProcessors();
         VirtualMachineConfig.Builder maximalBuilder =
@@ -282,7 +285,8 @@
                         .setApkPath("/apk/path")
                         .setNumCpus(maxCpus)
                         .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .setMemoryMib(42);
+                        .setMemoryMib(42)
+                        .setEncryptedStorageKib(1024);
         VirtualMachineConfig maximal = maximalBuilder.build();
 
         assertThat(maximal.getApkPath()).isEqualTo("/apk/path");
@@ -292,6 +296,8 @@
         assertThat(maximal.getPayloadBinaryPath()).isNull();
         assertThat(maximal.getPayloadConfigPath()).isEqualTo("config/path");
         assertThat(maximal.isProtectedVm()).isEqualTo(isProtectedVm());
+        assertThat(maximal.isEncryptedStorageEnabled()).isTrue();
+        assertThat(maximal.getEncryptedStorageKib()).isEqualTo(1024);
 
         assertThat(minimal.isCompatibleWith(maximal)).isFalse();
         assertThat(minimal.isCompatibleWith(minimal)).isTrue();
@@ -299,6 +305,10 @@
 
         VirtualMachineConfig compatible = maximalBuilder.setNumCpus(1).setMemoryMib(99).build();
         assertThat(compatible.isCompatibleWith(maximal)).isTrue();
+
+        // Assert that different encrypted storage size would imply the configs are incompatible
+        VirtualMachineConfig incompatible = minimalBuilder.setEncryptedStorageKib(1048).build();
+        assertThat(incompatible.isCompatibleWith(minimal)).isFalse();
     }
 
     @Test
@@ -927,13 +937,28 @@
     }
 
     @Test
-    public void importedVmIsEqualToTheOriginalVm() throws Exception {
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void importedVmIsEqualToTheOriginalVm_WithoutStorage() throws Exception {
+        TestResults testResults = importedVmIsEqualToTheOriginalVm(false);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void importedVmIsEqualToTheOriginalVm_WithStorage() throws Exception {
+        TestResults testResults = importedVmIsEqualToTheOriginalVm(true);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
+    }
+
+    private TestResults importedVmIsEqualToTheOriginalVm(boolean encryptedStoreEnabled)
+            throws Exception {
         // Arrange
-        VirtualMachineConfig config =
+        VirtualMachineConfig.Builder builder =
                 newVmConfigBuilder()
                         .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
-                        .setDebugLevel(DEBUG_LEVEL_FULL)
-                        .build();
+                        .setDebugLevel(DEBUG_LEVEL_FULL);
+        if (encryptedStoreEnabled) builder = builder.setEncryptedStorageKib(4096);
+        VirtualMachineConfig config = builder.build();
         String vmNameOrig = "test_vm_orig";
         String vmNameImport = "test_vm_import";
         VirtualMachine vmOrig = forceCreateNewVirtualMachine(vmNameOrig, config);
@@ -953,12 +978,34 @@
         // Asserts
         assertFileContentsAreEqualInTwoVms("config.xml", vmNameOrig, vmNameImport);
         assertFileContentsAreEqualInTwoVms("instance.img", vmNameOrig, vmNameImport);
+        if (encryptedStoreEnabled) {
+            assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
+        }
         assertThat(vmImport).isNotEqualTo(vmOrig);
         vmm.delete(vmNameOrig);
         assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
         TestResults testResults = runVmTestService(vmImport);
         assertThat(testResults.mException).isNull();
         assertThat(testResults.mAddInteger).isEqualTo(123 + 456);
+        return testResults;
+    }
+
+    @Test
+    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
+    public void encryptedStorageAvailable() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryPath("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setEncryptedStorageKib(4096)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+
+        TestResults testResults = runVmTestService(vm);
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("/mnt/encryptedstore");
     }
 
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 88e7ce0..072afec 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -880,8 +880,9 @@
 // Return whether a partition is exempt from selinux label checks, because we know that it does
 // not contain code and is likely to be generated in an app-writable directory.
 fn is_safe_app_partition(label: &str) -> bool {
-    // See make_payload_disk in payload.rs.
+    // See add_microdroid_system_images & add_microdroid_payload_images in payload.rs.
     label == "vm-instance"
+        || label == "encryptedstore"
         || label == "microdroid-apk-idsig"
         || label == "payload-metadata"
         || label.starts_with("extra-idsig-")
@@ -897,7 +898,7 @@
     match ctx.selinux_type()? {
         | "system_file" // immutable dm-verity protected partition
         | "apk_data_file" // APKs of an installed app
-        | "staging_data_file" // updated/staged APEX imagess
+        | "staging_data_file" // updated/staged APEX images
         | "shell_data_file" // test files created via adb shell
          => Ok(()),
         _ => bail!("Label {} is not allowed", ctx),