Merge "[refactor] Remove unused shutdownMicrodroid in MicrodroidHostTestCaseBase"
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 02459b2..601c6fc 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -32,10 +32,8 @@
 use log::{info, warn};
 use rustutils::system_properties;
 use std::fs::{self, File};
-use std::io::{BufRead, BufReader};
 use std::num::NonZeroU32;
 use std::path::{Path, PathBuf};
-use std::thread;
 use vmclient::{DeathReason, ErrorCode, VmInstance, VmWaitError};
 
 /// This owns an instance of the CompOS VM.
@@ -244,13 +242,6 @@
         log::info!("VM payload started, cid = {}", cid);
     }
 
-    fn on_payload_stdio(&self, cid: i32, stream: &File) {
-        if let Err(e) = start_logging(stream) {
-            log::warn!("Can't log vm output: {}", e);
-        };
-        log::info!("VM payload forwarded its stdio, cid = {}", cid);
-    }
-
     fn on_payload_ready(&self, cid: i32) {
         log::info!("VM payload ready, cid = {}", cid);
     }
@@ -267,19 +258,3 @@
         log::warn!("VM died, cid = {}, reason = {:?}", cid, death_reason);
     }
 }
-
-fn start_logging(file: &File) -> Result<()> {
-    let reader = BufReader::new(file.try_clone().context("Cloning file failed")?);
-    thread::spawn(move || {
-        for line in reader.lines() {
-            match line {
-                Ok(line) => info!("VM: {}", line),
-                Err(e) => {
-                    warn!("Reading VM output failed: {}", e);
-                    break;
-                }
-            }
-        }
-    });
-    Ok(())
-}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index c280956..a4e3903 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -24,10 +24,10 @@
 
 use anyhow::{bail, Result};
 use compos_common::COMPOS_VSOCK_PORT;
-use log::{debug, error, warn};
+use log::{debug, error};
 use rpcbinder::run_vsock_rpc_server;
 use std::panic;
-use vm_payload_bindgen::{AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy};
+use vm_payload_bindgen::AVmPayload_notifyPayloadReady;
 
 fn main() {
     if let Err(e) = try_main() {
@@ -44,10 +44,6 @@
     panic::set_hook(Box::new(|panic_info| {
         error!("{}", panic_info);
     }));
-    // Redirect stdio to the host.
-    if !unsafe { AVmPayload_setupStdioProxy() } {
-        warn!("Failed to setup stdio proxy");
-    }
 
     let service = compsvc::new_binder()?.as_binder();
     debug!("compsvc is starting as a rpc service.");
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index ebc2bb3..8e870ea 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -42,7 +42,6 @@
 import com.android.microdroid.testservice.ITestService;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -172,13 +171,6 @@
                         public void onPayloadStarted(VirtualMachine vm) {}
 
                         @Override
-                        public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
-                            mPayloadOutput.postValue("(Payload connected standard output...)");
-                            InputStream input = new FileInputStream(stream.getFileDescriptor());
-                            mService.execute(new Reader("payload", mPayloadOutput, input));
-                        }
-
-                        @Override
                         public void onPayloadReady(VirtualMachine vm) {
                             // This check doesn't 100% prevent race condition or UI hang.
                             // However, it's fine for demo.
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
new file mode 100644
index 0000000..301e733
--- /dev/null
+++ b/encryptedstore/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "encryptedstore.defaults",
+    srcs: ["src/main.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    rustlibs: [
+        "libandroid_logger",
+        "libanyhow",
+        "libclap",
+        "libhex",
+        "liblog_rust",
+        "libnix",
+        "libdm_rust",
+    ],
+    multilib: {
+        lib32: {
+            enabled: false,
+        },
+    },
+}
+
+rust_binary {
+    name: "encryptedstore",
+    defaults: ["encryptedstore.defaults"],
+    bootstrap: true,
+}
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
new file mode 100644
index 0000000..9a701ed
--- /dev/null
+++ b/encryptedstore/src/main.rs
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+//! `encryptedstore` is a program that (as the name indicates) provides encrypted storage
+//! solution in a VM. This is based on dm-crypt & requires the (64 bytes') key & the backing device.
+//! It uses dm_rust lib.
+
+use anyhow::{ensure, Context, Result};
+use clap::{arg, App};
+use dm::{crypt::CipherType, util};
+use log::info;
+use std::os::unix::fs::FileTypeExt;
+use std::path::Path;
+
+fn main() -> Result<()> {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("encryptedstore")
+            .with_min_level(log::Level::Info),
+    );
+    info!("Starting encryptedstore binary");
+
+    let matches = App::new("encryptedstore")
+        .args(&[
+            arg!(--blkdevice <FILE> "the block device backing the encrypted storage")
+                .required(true),
+            arg!(--key <KEY> "key (in hex) equivalent to 64 bytes)").required(true),
+        ])
+        .get_matches();
+
+    let blkdevice = Path::new(matches.value_of("blkdevice").unwrap());
+    let key = matches.value_of("key").unwrap();
+    ensure!(
+        std::fs::metadata(&blkdevice)
+            .context(format!("Failed to get metadata of {:?}", blkdevice))?
+            .file_type()
+            .is_block_device(),
+        "The path:{:?} is not of a block device",
+        blkdevice
+    );
+
+    enable_crypt(blkdevice, key, "cryptdev")?;
+    Ok(())
+}
+
+fn enable_crypt(data_device: &Path, key: &str, name: &str) -> Result<()> {
+    let dev_size = util::blkgetsize64(data_device)?;
+    let key = hex::decode(key).context("Unable to decode hex key")?;
+    ensure!(key.len() == 64, "We need 64 bytes' key for aes-xts cipher for block encryption");
+
+    // Create the dm-crypt spec
+    let target = dm::crypt::DmCryptTargetBuilder::default()
+        .data_device(data_device, dev_size)
+        .cipher(CipherType::AES256XTS) // TODO(b/259253336) Move to HCTR2 based encryption.
+        .key(&key)
+        .build()
+        .context("Couldn't build the DMCrypt target")?;
+    let dm = dm::DeviceMapper::new()?;
+    dm.create_crypt_device(name, &target).context("Failed to create dm-crypt device")?;
+
+    Ok(())
+}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index d9c75c0..4435576 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -642,12 +642,6 @@
                             }
 
                             @Override
-                            public void onPayloadStdio(int cid, ParcelFileDescriptor stream) {
-                                executeCallback(
-                                        (cb) -> cb.onPayloadStdio(VirtualMachine.this, stream));
-                            }
-
-                            @Override
                             public void onPayloadReady(int cid) {
                                 executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
                             }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index 26b8ba2..1f94a8b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -137,9 +137,6 @@
     /** Called when the payload starts in the VM. */
     void onPayloadStarted(@NonNull VirtualMachine vm);
 
-    /** Called when the payload creates a standard input/output stream. */
-    void onPayloadStdio(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor stream);
-
     /**
      * Called when the payload in the VM is ready to serve. See
      * {@link VirtualMachine#connectToVsockServer(int)}.
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 91801ff..d73a03c 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -107,6 +107,7 @@
                 "apkdmverity",
                 "authfs",
                 "authfs_service",
+                "encryptedstore",
                 "microdroid_crashdump_kernel",
                 "microdroid_kexec",
                 "microdroid_manager",
diff --git a/microdroid/vm_payload/include/vm_payload.h b/microdroid/vm_payload/include/vm_payload.h
index d5853a1..48518ff 100644
--- a/microdroid/vm_payload/include/vm_payload.h
+++ b/microdroid/vm_payload/include/vm_payload.h
@@ -56,8 +56,9 @@
                                   void (*on_ready)(void *param), void *param);
 
 /**
- * Get a secret that is uniquely bound to this VM instance. The secrets are 32-byte values and the
- * value associated with an identifier will not change over the lifetime of the VM instance.
+ * Get a secret that is uniquely bound to this VM instance. The secrets are
+ * values up to 32 bytes long and the value associated with an identifier will
+ * not change over the lifetime of the VM instance.
  *
  * \param identifier identifier of the secret to return.
  * \param identifier_size size of the secret identifier.
@@ -81,12 +82,16 @@
 const char *AVmPayload_getApkContentsPath(void);
 
 /**
- * Initiates a socket connection with the host and duplicates stdin, stdout and
- * stderr file descriptors to the socket.
+ * Gets the path to the encrypted persistent storage for the VM, if any. This is
+ * a directory under which any files or directories created will be stored on
+ * behalf of the VM by the host app. All data is encrypted using a key known
+ * only to the VM, so the host cannot decrypt it, but may delete it.
  *
- * \return true on success and false on failure. If unsuccessful, the stdio FDs
- * may be in an inconsistent state.
+ * \return the path to the APK contents, or NULL if no encrypted storage was
+ * requested in the VM configuration. If non-null the returned string should not
+ * be deleted or freed by the application and remains valid for the lifetime of
+ * the VM.
  */
-bool AVmPayload_setupStdioProxy();
+const char *AVmPayload_getEncryptedStoragePath(void);
 
 __END_DECLS
diff --git a/microdroid/vm_payload/src/lib.rs b/microdroid/vm_payload/src/lib.rs
index 65b59bf..be6cf93 100644
--- a/microdroid/vm_payload/src/lib.rs
+++ b/microdroid/vm_payload/src/lib.rs
@@ -18,5 +18,5 @@
 
 pub use vm_payload_service::{
     AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
-    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_setupStdioProxy,
+    AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady,
 };
diff --git a/microdroid/vm_payload/src/vm_payload_service.rs b/microdroid/vm_payload/src/vm_payload_service.rs
index e89f730..88484cc 100644
--- a/microdroid/vm_payload/src/vm_payload_service.rs
+++ b/microdroid/vm_payload/src/vm_payload_service.rs
@@ -21,24 +21,46 @@
 use lazy_static::lazy_static;
 use log::{error, info, Level};
 use rpcbinder::{get_unix_domain_rpc_interface, run_vsock_rpc_server};
-use std::io;
 use std::ffi::CString;
-use std::fs::File;
 use std::os::raw::{c_char, c_void};
-use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd};
+use std::ptr;
+use std::sync::Mutex;
 
 lazy_static! {
     static ref VM_APK_CONTENTS_PATH_C: CString =
         CString::new(VM_APK_CONTENTS_PATH).expect("CString::new failed");
+    static ref PAYLOAD_CONNECTION: Mutex<Option<Strong<dyn IVmPayloadService>>> = Mutex::default();
+}
+
+/// Return a connection to the payload service in Microdroid Manager. Uses the existing connection
+/// if there is one, otherwise attempts to create a new one.
+fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
+    let mut connection = PAYLOAD_CONNECTION.lock().unwrap();
+    if let Some(strong) = &*connection {
+        Ok(strong.clone())
+    } else {
+        let new_connection: Strong<dyn IVmPayloadService> = get_unix_domain_rpc_interface(
+            VM_PAYLOAD_SERVICE_SOCKET_NAME,
+        )
+        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))?;
+        *connection = Some(new_connection.clone());
+        Ok(new_connection)
+    }
+}
+
+/// Make sure our logging goes to logcat. It is harmless to call this more than once.
+fn initialize_logging() {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
+    );
 }
 
 /// Notifies the host that the payload is ready.
 /// Returns true if the notification succeeds else false.
 #[no_mangle]
 pub extern "C" fn AVmPayload_notifyPayloadReady() -> bool {
-    android_logger::init_once(
-        android_logger::Config::default().with_tag("vm_payload").with_min_level(Level::Debug),
-    );
+    initialize_logging();
+
     if let Err(e) = try_notify_payload_ready() {
         error!("{:?}", e);
         false
@@ -77,6 +99,8 @@
     on_ready: Option<unsafe extern "C" fn(param: *mut c_void)>,
     param: *mut c_void,
 ) -> bool {
+    initialize_logging();
+
     // SAFETY: AIBinder returned has correct reference count, and the ownership can
     // safely be taken by new_spibinder.
     let service = new_spibinder(service);
@@ -109,6 +133,8 @@
     secret: *mut u8,
     size: usize,
 ) -> bool {
+    initialize_logging();
+
     let identifier = std::slice::from_raw_parts(identifier, identifier_size);
     match try_get_vm_instance_secret(identifier, size) {
         Err(e) => {
@@ -148,6 +174,8 @@
     size: usize,
     total: *mut usize,
 ) -> bool {
+    initialize_logging();
+
     match try_get_dice_attestation_chain() {
         Err(e) => {
             error!("{:?}", e);
@@ -182,6 +210,8 @@
     size: usize,
     total: *mut usize,
 ) -> bool {
+    initialize_logging();
+
     match try_get_dice_attestation_cdi() {
         Err(e) => {
             error!("{:?}", e);
@@ -201,41 +231,13 @@
     (*VM_APK_CONTENTS_PATH_C).as_ptr()
 }
 
+/// Gets the path to the VM's encrypted storage.
+#[no_mangle]
+pub extern "C" fn AVmPayload_getEncryptedStoragePath() -> *const c_char {
+    // TODO(b/254454578): Return a real path if storage is present
+    ptr::null()
+}
+
 fn try_get_dice_attestation_cdi() -> Result<Vec<u8>> {
     get_vm_payload_service()?.getDiceAttestationCdi().context("Cannot get attestation CDI")
 }
-
-/// Creates a socket connection with the host and duplicates standard I/O
-/// file descriptors of the payload to that socket. Then notifies the host.
-#[no_mangle]
-pub extern "C" fn AVmPayload_setupStdioProxy() -> bool {
-    if let Err(e) = try_setup_stdio_proxy() {
-        error!("{:?}", e);
-        false
-    } else {
-        info!("Successfully set up stdio proxy to the host");
-        true
-    }
-}
-
-fn dup2(old_fd: &File, new_fd: BorrowedFd) -> Result<(), io::Error> {
-    // SAFETY - ownership does not change, only modifies the underlying raw FDs.
-    match unsafe { libc::dup2(old_fd.as_raw_fd(), new_fd.as_raw_fd()) } {
-        -1 => Err(io::Error::last_os_error()),
-        _ => Ok(()),
-    }
-}
-
-fn try_setup_stdio_proxy() -> Result<()> {
-    let fd =
-        get_vm_payload_service()?.setupStdioProxy().context("Could not connect a host socket")?;
-    dup2(fd.as_ref(), io::stdin().as_fd()).context("Failed to dup stdin")?;
-    dup2(fd.as_ref(), io::stdout().as_fd()).context("Failed to dup stdout")?;
-    dup2(fd.as_ref(), io::stderr().as_fd()).context("Failed to dup stderr")?;
-    Ok(())
-}
-
-fn get_vm_payload_service() -> Result<Strong<dyn IVmPayloadService>> {
-    get_unix_domain_rpc_interface(VM_PAYLOAD_SERVICE_SOCKET_NAME)
-        .context(format!("Failed to connect to service: {}", VM_PAYLOAD_SERVICE_SOCKET_NAME))
-}
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 4b06b3e..44b4c01 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
         "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
+        "libhex",
         "libitertools",
         "libkernlog",
         "libkeystore2_crypto_rust",
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 1141965..f8e7d34 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,8 +16,6 @@
 
 package android.system.virtualization.payload;
 
-import android.os.ParcelFileDescriptor;
-
 /**
  * This interface regroups the tasks that payloads delegate to
  * Microdroid Manager for execution.
@@ -63,16 +61,4 @@
      * @throws SecurityException if the use of test APIs is not permitted.
      */
     byte[] getDiceAttestationCdi();
-
-    /**
-     * Sets up a standard I/O proxy to the host.
-     *
-     * Creates a socket with the host and notifies its listeners that the stdio
-     * proxy is ready.
-     *
-     * Temporarily uses a random free port allocated by the OS.
-     * @return a file descriptor that the payload should dup() its standard I/O
-     * file descriptors to.
-     */
-    ParcelFileDescriptor setupStdioProxy();
 }
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 3881db3..499835f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -38,6 +38,16 @@
     pub bcc: Vec<u8>,
 }
 
+impl DiceContext {
+    pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data based on salt. Use different salt
+        // for different keys.
+        let mut key = ZVec::new(keysize as usize)?;
+        hkdf(&mut key, Md::sha256(), &self.cdi_seal, salt, identifier)?;
+        Ok(key)
+    }
+}
+
 /// Artifacts that are mapped into the process address space from the driver.
 pub enum DiceDriver<'a> {
     Real {
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index ef8f5f5..4ebcc2e 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -83,6 +83,12 @@
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
 
+/// Identifier for the key used for encrypted store.
+const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
+const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
+const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
     #[error("Cannot connect to virtualization service: {0}")]
@@ -365,7 +371,15 @@
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
+    let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+
+    // Run encryptedstore binary to prepare the storage
+    let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+        info!("Preparing encryptedstore ...");
+        Some(prepare_encryptedstore(&dice_context).context("encryptedstore run")?)
+    } else {
+        None
+    };
 
     // Before reading a file from the APK, start zipfuse
     run_zipfuse(
@@ -419,7 +433,12 @@
     // 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")?;
 
-    register_vm_payload_service(allow_restricted_apis, service.clone(), dice)?;
+    register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+
+    if let Some(mut child) = encryptedstore_child {
+        let exitcode = child.wait().context("Wait for encryptedstore child")?;
+        ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
+    }
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
     exec_task(task, service).context("Failed to run payload")
@@ -780,3 +799,27 @@
 fn to_hex_string(buf: &[u8]) -> String {
     buf.iter().map(|b| format!("{:02X}", b)).collect()
 }
+
+fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+    // Use a fixed salt to scope the derivation to this API.
+    // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
+    // TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
+    let salt = [
+        0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D,
+        0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
+        0x4A, 0x75,
+    ];
+    let key = dice.get_sealing_key(
+        &salt,
+        ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
+        ENCRYPTEDSTORE_KEYSIZE,
+    )?;
+
+    let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
+    cmd.arg("--blkdevice")
+        .arg(ENCRYPTEDSTORE_BACKING_DEVICE)
+        .arg("--key")
+        .arg(hex::encode(&*key))
+        .spawn()
+        .context("encryptedstore failed")
+}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 126a8a9..fcfc79d 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -18,18 +18,15 @@
 use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
     BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
-use anyhow::{bail, Context, Result};
-use binder::{Interface, BinderFeatures, ExceptionCode, ParcelFileDescriptor, Status, Strong};
+use anyhow::{bail, Result};
+use binder::{Interface, BinderFeatures, ExceptionCode, Status, Strong};
 use log::{error, info};
 use openssl::hkdf::hkdf;
 use openssl::md::Md;
 use rpcbinder::run_init_unix_domain_rpc_server;
-use std::fs::File;
 use std::sync::mpsc;
 use std::thread;
 use std::time::Duration;
-use std::os::unix::io::{FromRawFd, IntoRawFd};
-use vsock::VsockListener;
 
 /// Implementation of `IVmPayloadService`.
 struct VmPayloadService {
@@ -70,16 +67,6 @@
         self.check_restricted_apis_allowed()?;
         Ok(self.dice.cdi_attest.to_vec())
     }
-
-    fn setupStdioProxy(&self) -> binder::Result<ParcelFileDescriptor> {
-        let f = self.setup_payload_stdio_proxy().map_err(|e| {
-            Status::new_service_specific_error_str(
-                -1,
-                Some(format!("Failed to create stdio proxy: {:?}", e)),
-            )
-        })?;
-        Ok(ParcelFileDescriptor::new(f))
-    }
 }
 
 impl Interface for VmPayloadService {}
@@ -102,22 +89,6 @@
             Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Use of restricted APIs")))
         }
     }
-
-    fn setup_payload_stdio_proxy(&self) -> Result<File> {
-        // Instead of a predefined port in the host, we open up a port in the guest and have
-        // the host connect to it. This makes it possible to have per-app instances of VS.
-        const ANY_PORT: u32 = u32::MAX; // (u32)-1
-        let listener = VsockListener::bind_with_cid_port(libc::VMADDR_CID_ANY, ANY_PORT)
-            .context("Failed to create vsock listener")?;
-        let addr = listener.local_addr().context("Failed to resolve listener port")?;
-        self.virtual_machine_service
-            .connectPayloadStdioProxy(addr.port() as i32)
-            .context("Failed to connect to the host")?;
-        let (stream, _) =
-            listener.accept().context("Failed to accept vsock connection from the host")?;
-        // SAFETY: ownership is transferred from stream to the new File
-        Ok(unsafe { File::from_raw_fd(stream.into_raw_fd()) })
-    }
 }
 
 /// Registers the `IVmPayloadService` service.
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index e8c435f..eda4f75 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -36,4 +36,7 @@
 
     /* get the APK contents path. */
     String getApkContentsPath();
+
+    /* get the encrypted storage path. */
+    String getEncryptedStoragePath();
 }
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 6f44ff3..24e2049 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
@@ -229,9 +229,6 @@
         public void onPayloadStarted(VirtualMachine vm) {}
 
         @Override
-        public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {}
-
-        @Override
         public void onPayloadReady(VirtualMachine vm) {}
 
         @Override
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 eb719b8..da7c7ec 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -27,7 +27,6 @@
 
 import android.content.Context;
 import android.os.Build;
-import android.os.ParcelFileDescriptor;
 import android.os.ServiceSpecificException;
 import android.os.SystemProperties;
 import android.system.virtualmachine.VirtualMachine;
@@ -122,6 +121,7 @@
         assertThat(testResults.mAppRunProp).isEqualTo("true");
         assertThat(testResults.mSublibRunProp).isEqualTo("true");
         assertThat(testResults.mApkContentsPath).isEqualTo("/mnt/apk");
+        assertThat(testResults.mEncryptedStoragePath).isEqualTo("");
     }
 
     @Test
@@ -708,6 +708,7 @@
         String mSublibRunProp;
         String mExtraApkTestProp;
         String mApkContentsPath;
+        String mEncryptedStoragePath;
     }
 
     private TestResults runVmTestService(VirtualMachine vm) throws Exception {
@@ -729,6 +730,8 @@
                             testResults.mExtraApkTestProp =
                                     testService.readProperty("debug.microdroid.test.extra_apk");
                             testResults.mApkContentsPath = testService.getApkContentsPath();
+                            testResults.mEncryptedStoragePath =
+                                    testService.getEncryptedStoragePath();
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
@@ -747,13 +750,6 @@
                         Log.i(TAG, "onPayloadStarted");
                         payloadStarted.complete(true);
                     }
-
-                    @Override
-                    public void onPayloadStdio(VirtualMachine vm, ParcelFileDescriptor stream) {
-                        Log.i(TAG, "onPayloadStdio");
-                        logVmOutput(
-                                TAG, new FileInputStream(stream.getFileDescriptor()), "Payload");
-                    }
                 };
         listener.runToFinish(TAG, vm);
         assertThat(payloadStarted.getNow(false)).isTrue();
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 1b18ce9..694f452 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -123,6 +123,16 @@
             *out = path;
             return ndk::ScopedAStatus::ok();
         }
+
+        ndk::ScopedAStatus getEncryptedStoragePath(std::string* out) override {
+            const char* path_c = AVmPayload_getEncryptedStoragePath();
+            if (path_c == nullptr) {
+                out->clear();
+            } else {
+                *out = path_c;
+            }
+            return ndk::ScopedAStatus::ok();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
 
@@ -158,9 +168,6 @@
 } // Anonymous namespace
 
 extern "C" int AVmPayload_main() {
-    // Forward standard I/O to the host.
-    AVmPayload_setupStdioProxy();
-
     // disable buffering to communicate seamlessly
     setvbuf(stdin, nullptr, _IONBF, 0);
     setvbuf(stdout, nullptr, _IONBF, 0);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
index 521cf12..a329fa6 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachineCallback.aidl
@@ -29,11 +29,6 @@
     void onPayloadStarted(int cid);
 
     /**
-     * Called when the payload provides access to its standard input/output via a socket.
-     */
-    void onPayloadStdio(int cid, in ParcelFileDescriptor fd);
-
-    /**
      * Called when the payload in the VM is ready to serve.
      */
     void onPayloadReady(int cid);
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index deee662..f2d92af 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -50,9 +50,4 @@
      * Notifies that an error has occurred inside the VM.
      */
     void notifyError(ErrorCode errorCode, in String message);
-
-    /**
-     * Notifies that the guest has started a stdio proxy on the given port.
-     */
-    void connectPayloadStdioProxy(int port);
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 2ec39e8..cfcfa2b 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -953,16 +953,6 @@
         }
     }
 
-    /// Call all registered callbacks to notify that the payload has provided a standard I/O proxy.
-    pub fn notify_payload_stdio(&self, cid: Cid, fd: ParcelFileDescriptor) {
-        let callbacks = &*self.0.lock().unwrap();
-        for callback in callbacks {
-            if let Err(e) = callback.onPayloadStdio(cid as i32, &fd) {
-                error!("Error notifying payload stdio event from VM CID {}: {:?}", cid, e);
-            }
-        }
-    }
-
     /// Call all registered callbacks to say that the VM has died.
     pub fn callback_on_died(&self, cid: Cid, reason: DeathReason) {
         let callbacks = &*self.0.lock().unwrap();
@@ -1187,27 +1177,6 @@
             ))
         }
     }
-
-    fn connectPayloadStdioProxy(&self, port: i32) -> binder::Result<()> {
-        let cid = self.cid;
-        if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
-            info!("VM with CID {} started a stdio proxy", cid);
-            let stream = VsockStream::connect_with_cid_port(cid, port as u32).map_err(|e| {
-                Status::new_service_specific_error_str(
-                    -1,
-                    Some(format!("Failed to connect to guest stdio proxy: {:?}", e)),
-                )
-            })?;
-            vm.callbacks.notify_payload_stdio(cid, vsock_stream_to_pfd(stream));
-            Ok(())
-        } else {
-            error!("connectPayloadStdioProxy is called from an unknown CID {}", cid);
-            Err(Status::new_service_specific_error_str(
-                -1,
-                Some(format!("cannot find a VM with CID {}", cid)),
-            ))
-        }
-    }
 }
 
 impl VirtualMachineService {
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 233e74b..f6e8a7b 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -420,7 +420,7 @@
 
     if let Some(file) = storage_image {
         writable_partitions.push(Partition {
-            label: "encrypted-storage".to_owned(),
+            label: "encryptedstore".to_owned(),
             image: Some(ParcelFileDescriptor::new(file)),
             writable: true,
         });
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 7cd5a19..1f0433d 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -27,7 +27,7 @@
 use binder::ParcelFileDescriptor;
 use microdroid_payload_config::VmPayloadConfig;
 use std::fs::File;
-use std::io::{self, BufRead, BufReader};
+use std::io;
 use std::os::unix::io::{AsRawFd, FromRawFd};
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
@@ -280,20 +280,6 @@
         eprintln!("payload started");
     }
 
-    fn on_payload_stdio(&self, _cid: i32, stream: &File) {
-        eprintln!("connecting payload stdio...");
-        // Show the output of the payload
-        let mut reader = BufReader::new(stream.try_clone().unwrap());
-        std::thread::spawn(move || loop {
-            let mut s = String::new();
-            match reader.read_line(&mut s) {
-                Ok(0) => break,
-                Ok(_) => print!("{}", s),
-                Err(e) => eprintln!("error reading from virtual machine: {}", e),
-            };
-        });
-    }
-
     fn on_payload_ready(&self, _cid: i32) {
         eprintln!("payload is ready");
     }
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 1dd553c..20b7f02 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -80,9 +80,6 @@
     /// clients.
     fn on_payload_ready(&self, cid: i32) {}
 
-    /// Called by the payload to forward its standard I/O streams to the host.
-    fn on_payload_stdio(&self, cid: i32, fd: &File);
-
     /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
     /// process.
     fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
@@ -280,13 +277,6 @@
         Ok(())
     }
 
-    fn onPayloadStdio(&self, cid: i32, stream: &ParcelFileDescriptor) -> BinderResult<()> {
-        if let Some(ref callback) = self.client_callback {
-            callback.on_payload_stdio(cid, stream.as_ref());
-        }
-        Ok(())
-    }
-
     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::READY);
         if let Some(ref callback) = self.client_callback {