Merge "TerminalApp: Use sparse file for debian rootfs if storage balloon enabled" into main
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index e1e236a..c18ada4 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -14,6 +14,7 @@
         // TODO(b/330257000): will be removed when binder RPC is used
         "android.system.virtualizationservice_internal-java",
         "androidx-constraintlayout_constraintlayout",
+        "androidx.navigation_navigation-fragment-ktx",
         "androidx.window_window",
         "androidx.work_work-runtime",
         "apache-commons-compress",
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
index 23bf48d..086ff3d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstalledImage.kt
@@ -120,11 +120,12 @@
         val roundedUpDesiredSize = roundUp(desiredSize)
         val curSize = getSize()
 
+        runE2fsck(rootPartition)
+
         if (roundedUpDesiredSize == curSize) {
             return roundedUpDesiredSize
         }
 
-        runE2fsck(rootPartition)
         if (roundedUpDesiredSize > curSize) {
             allocateSpace(rootPartition, roundedUpDesiredSize)
         }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
index 4162247..ba03716 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
@@ -47,6 +47,10 @@
         }
 
         try {
+            if (Files.isRegularFile(dir)) {
+                Log.i(tag, "Removed legacy log file: $dir")
+                Files.delete(dir)
+            }
             Files.createDirectories(dir)
             deleteOldLogs(dir, 10)
             val logPath = dir.resolve(LocalDateTime.now().toString() + ".txt")
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
index f4306bf..d85242b 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -45,7 +45,7 @@
 import androidx.activity.result.ActivityResultCallback
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.lifecycle.ViewModelProvider
+import androidx.activity.viewModels
 import androidx.viewpager2.widget.ViewPager2
 import com.android.internal.annotations.VisibleForTesting
 import com.android.microdroid.test.common.DeviceProperties
@@ -72,11 +72,11 @@
     private lateinit var image: InstalledImage
     private lateinit var accessibilityManager: AccessibilityManager
     private lateinit var manageExternalStorageActivityResultLauncher: ActivityResultLauncher<Intent>
-    private lateinit var terminalViewModel: TerminalViewModel
     private lateinit var viewPager: ViewPager2
     private lateinit var tabLayout: TabLayout
     private lateinit var terminalTabAdapter: TerminalTabAdapter
     private val terminalInfo = CompletableFuture<TerminalInfo>()
+    private val terminalViewModel: TerminalViewModel by viewModels()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -111,7 +111,6 @@
     }
 
     private fun initializeUi() {
-        terminalViewModel = ViewModelProvider(this)[TerminalViewModel::class.java]
         setContentView(R.layout.activity_headless)
         tabLayout = findViewById<TabLayout>(R.id.tab_layout)
         displayMenu = findViewById<Button>(R.id.display_button)
@@ -151,6 +150,20 @@
         TabLayoutMediator(tabLayout, viewPager, false, false) { _: TabLayout.Tab?, _: Int -> }
             .attach()
 
+        tabLayout.addOnTabSelectedListener(
+            object : TabLayout.OnTabSelectedListener {
+                override fun onTabSelected(tab: TabLayout.Tab?) {
+                    tab?.position?.let {
+                        terminalViewModel.selectedTabViewId = terminalTabAdapter.tabs[it].id
+                    }
+                }
+
+                override fun onTabUnselected(tab: TabLayout.Tab?) {}
+
+                override fun onTabReselected(tab: TabLayout.Tab?) {}
+            }
+        )
+
         addTerminalTab()
 
         tabAddButton?.setOnClickListener { addTerminalTab() }
@@ -160,7 +173,9 @@
         val tab = tabLayout.newTab()
         tab.setCustomView(R.layout.tabitem_terminal)
         viewPager.offscreenPageLimit += 1
-        terminalTabAdapter.addTab()
+        val tabId = terminalTabAdapter.addTab()
+        terminalViewModel.selectedTabViewId = tabId
+        terminalViewModel.terminalTabs[tabId] = tab
         tab.customView!!
             .findViewById<Button>(R.id.tab_close_button)
             .setOnClickListener(
@@ -223,9 +238,7 @@
                 "&fontWeightBold=" +
                 (FontStyle.FONT_WEIGHT_BOLD + config.fontWeightAdjustment) +
                 "&screenReaderMode=" +
-                accessibilityManager.isEnabled +
-                "&titleFixed=" +
-                getString(R.string.app_name))
+                accessibilityManager.isEnabled)
 
         try {
             return URL("https", ipAddress, port, "/$query")
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
index 7e78235..a0c6e4e 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
@@ -31,8 +31,9 @@
 import android.webkit.WebSettings
 import android.webkit.WebView
 import android.webkit.WebViewClient
+import android.widget.TextView
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
+import androidx.fragment.app.activityViewModels
 import com.android.system.virtualmachine.flags.Flags.terminalGuiSupport
 import com.android.virtualization.terminal.CertificateUtils.createOrGetKey
 import com.android.virtualization.terminal.CertificateUtils.writeCertificateToFile
@@ -45,7 +46,7 @@
     private lateinit var id: String
     private var certificates: Array<X509Certificate>? = null
     private var privateKey: PrivateKey? = null
-    private lateinit var terminalViewModel: TerminalViewModel
+    private val terminalViewModel: TerminalViewModel by activityViewModels()
 
     override fun onCreateView(
         inflater: LayoutInflater,
@@ -59,7 +60,6 @@
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        terminalViewModel = ViewModelProvider(this)[TerminalViewModel::class.java]
         terminalView = view.findViewById(R.id.webview)
         bootProgressView = view.findViewById(R.id.boot_progress)
         initializeWebView()
@@ -79,19 +79,46 @@
         terminalView.saveState(outState)
     }
 
+    override fun onResume() {
+        super.onResume()
+        updateFocus()
+    }
+
     private fun initializeWebView() {
         terminalView.settings.databaseEnabled = true
         terminalView.settings.domStorageEnabled = true
         terminalView.settings.javaScriptEnabled = true
         terminalView.settings.cacheMode = WebSettings.LOAD_DEFAULT
 
-        terminalView.webChromeClient = WebChromeClient()
+        terminalView.webChromeClient = TerminalWebChromeClient()
         terminalView.webViewClient = TerminalWebViewClient()
 
         (activity as MainActivity).modifierKeysController.addTerminalView(terminalView)
         terminalViewModel.terminalViews.add(terminalView)
     }
 
+    private inner class TerminalWebChromeClient : WebChromeClient() {
+        override fun onReceivedTitle(view: WebView?, title: String?) {
+            super.onReceivedTitle(view, title)
+            title?.let { originalTitle ->
+                val ttydSuffix = " | login -f droid (localhost)"
+                val displayedTitle =
+                    if (originalTitle.endsWith(ttydSuffix)) {
+                        // When the session is created. The format of the title will be
+                        // 'droid@localhost: ~ | login -f droid (localhost)'.
+                        originalTitle.dropLast(ttydSuffix.length)
+                    } else {
+                        originalTitle
+                    }
+
+                terminalViewModel.terminalTabs[id]
+                    ?.customView
+                    ?.findViewById<TextView>(R.id.tab_title)
+                    ?.text = displayedTitle
+            }
+        }
+    }
+
     private inner class TerminalWebViewClient : WebViewClient() {
         private var loadFailed = false
         private var requestId: Long = 0
@@ -148,6 +175,7 @@
                             terminalView.visibility = View.VISIBLE
                             terminalView.mapTouchToMouseEvent()
                             updateMainActivity()
+                            updateFocus()
                         }
                     }
                 },
@@ -189,6 +217,12 @@
         certificates = arrayOf<X509Certificate>(pke.certificate as X509Certificate)
     }
 
+    private fun updateFocus() {
+        if (terminalViewModel.selectedTabViewId == id) {
+            terminalView.requestFocus()
+        }
+    }
+
     companion object {
         const val TAG: String = "VmTerminalApp"
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalViewModel.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalViewModel.kt
index 4a69f75..dd40143 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalViewModel.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalViewModel.kt
@@ -16,7 +16,10 @@
 package com.android.virtualization.terminal
 
 import androidx.lifecycle.ViewModel
+import com.google.android.material.tabs.TabLayout.Tab
 
 class TerminalViewModel : ViewModel() {
     val terminalViews: MutableSet<TerminalView> = mutableSetOf()
+    var selectedTabViewId: String? = null
+    val terminalTabs: MutableMap<String, Tab> = mutableMapOf()
 }
diff --git a/android/TerminalApp/res/layout/tabitem_terminal.xml b/android/TerminalApp/res/layout/tabitem_terminal.xml
index 92e3802..9eba163 100644
--- a/android/TerminalApp/res/layout/tabitem_terminal.xml
+++ b/android/TerminalApp/res/layout/tabitem_terminal.xml
@@ -25,7 +25,7 @@
       android:layout_alignParentStart="true"
       android:layout_alignParentTop="true"
       android:layout_toStartOf="@id/tab_close_button"
-      android:gravity="center"
+      android:gravity="center_vertical"
       android:padding="8dp"
       android:text="@string/tab_default_title"/>
 
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 1c4c2eb..d7f68b8 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -597,9 +597,10 @@
         config: &VirtualMachineConfig,
     ) -> binder::Result<(VmContext, Cid, PathBuf)> {
         const NUM_ATTEMPTS: usize = 5;
+        let name = get_name(config);
 
         for _ in 0..NUM_ATTEMPTS {
-            let vm_context = GLOBAL_SERVICE.allocateGlobalVmContext(requester_debug_pid)?;
+            let vm_context = GLOBAL_SERVICE.allocateGlobalVmContext(name, requester_debug_pid)?;
             let cid = vm_context.getCid()? as Cid;
             let temp_dir: PathBuf = vm_context.getTemporaryDirectory()?.into();
 
@@ -797,9 +798,8 @@
                 .or_service_specific_exception(-1)?;
         }
 
-        // Check if files for payloads and bases are NOT coming from /vendor and /odm, as they may
-        // have unstable interfaces.
-        // TODO(b/316431494): remove once Treble interfaces are stabilized.
+        // Check if files for payloads and bases are on the same side of the Treble boundary as the
+        // calling process, as they may have unstable interfaces.
         check_partitions_for_files(config, calling_partition).or_service_specific_exception(-1)?;
 
         let zero_filler_path = temporary_directory.join("zero.img");
@@ -1053,6 +1053,14 @@
     }
 }
 
+/// Returns the name of the config
+fn get_name(config: &VirtualMachineConfig) -> &str {
+    match config {
+        VirtualMachineConfig::AppConfig(config) => &config.name,
+        VirtualMachineConfig::RawConfig(config) => &config.name,
+    }
+}
+
 fn extract_vendor_hashtree_digest(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
     let VirtualMachineConfig::AppConfig(config) = config else {
         return Ok(None);
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
index 9f033b1..eb71028 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineDebugInfo.aidl
@@ -19,6 +19,9 @@
 
 /** Information about a running VM, for debug purposes only. */
 parcelable VirtualMachineDebugInfo {
+    /** Name of the VM. */
+    String name;
+
     /** The CID assigned to the VM. */
     int cid;
 
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
index 2cf4efd..4ded2a9 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVfioHandler.aidl
@@ -16,7 +16,6 @@
 package android.system.virtualizationservice_internal;
 
 import android.system.virtualizationservice.AssignableDevice;
-import android.system.virtualizationservice.VirtualMachineDebugInfo;
 import android.system.virtualizationservice_internal.AtomVmBooted;
 import android.system.virtualizationservice_internal.AtomVmCreationRequested;
 import android.system.virtualizationservice_internal.AtomVmExited;
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 4f549cb..3d4a813 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -39,7 +39,7 @@
      * The resources will not be recycled as long as there is a strong reference
      * to the returned object.
      */
-    IGlobalVmContext allocateGlobalVmContext(int requesterDebugPid);
+    IGlobalVmContext allocateGlobalVmContext(String name, int requesterDebugPid);
 
     /** Forwards a VmBooted atom to statsd. */
     void atomVmBooted(in AtomVmBooted atom);
diff --git a/android/virtualizationservice/src/aidl.rs b/android/virtualizationservice/src/aidl.rs
index 62cede8..1646117 100644
--- a/android/virtualizationservice/src/aidl.rs
+++ b/android/virtualizationservice/src/aidl.rs
@@ -273,6 +273,7 @@
 
     fn allocateGlobalVmContext(
         &self,
+        name: &str,
         requester_debug_pid: i32,
     ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
         check_manage_access()?;
@@ -281,7 +282,7 @@
         let requester_debug_pid = requester_debug_pid as pid_t;
         let state = &mut *self.state.lock().unwrap();
         state
-            .allocate_vm_context(requester_uid, requester_debug_pid)
+            .allocate_vm_context(name, requester_uid, requester_debug_pid)
             .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
     }
 
@@ -311,6 +312,7 @@
             .map(|vm| {
                 let vm = vm.lock().unwrap();
                 VirtualMachineDebugInfo {
+                    name: vm.name.clone(),
                     cid: vm.cid as i32,
                     temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
                     requesterUid: vm.requester_uid as i32,
@@ -665,6 +667,8 @@
 
 #[derive(Debug, Default)]
 struct GlobalVmInstance {
+    /// Name of the VM
+    name: String,
     /// The unique CID assigned to the VM for vsock communication.
     cid: Cid,
     /// UID of the client who requested this VM instance.
@@ -760,6 +764,7 @@
 
     fn allocate_vm_context(
         &mut self,
+        name: &str,
         requester_uid: uid_t,
         requester_debug_pid: pid_t,
     ) -> Result<Strong<dyn IGlobalVmContext>> {
@@ -768,6 +773,7 @@
 
         let cid = self.get_next_available_cid()?;
         let instance = Arc::new(Mutex::new(GlobalVmInstance {
+            name: name.to_owned(),
             cid,
             requester_uid,
             requester_debug_pid,
diff --git a/build/debian/fai_config/scripts/AVF/20-useradd b/build/debian/fai_config/scripts/AVF/20-useradd
index b92648a..2289a2a 100755
--- a/build/debian/fai_config/scripts/AVF/20-useradd
+++ b/build/debian/fai_config/scripts/AVF/20-useradd
@@ -2,3 +2,7 @@
 
 $ROOTCMD useradd -m -u 1000 -N -G sudo,video,render -s /usr/bin/bash droid
 $ROOTCMD echo 'droid ALL=(ALL) NOPASSWD:ALL' >> $target/etc/sudoers
+$ROOTCMD cat >> $target/home/droid/.bashrc <<EOF
+# Show title of current running command
+trap 'echo -ne "\e]0;\$BASH_COMMAND\007"' DEBUG
+EOF
diff --git a/guest/microdroid_manager/microdroid_manager.rc b/guest/microdroid_manager/microdroid_manager.rc
index 9fa8a9d..48cc6d7 100644
--- a/guest/microdroid_manager/microdroid_manager.rc
+++ b/guest/microdroid_manager/microdroid_manager.rc
@@ -8,6 +8,7 @@
     # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
     # CAP_SETPCAP is required to allow microdroid_manager to drop capabilities
     #   before executing the payload
-    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP SETUID SETGID
+    # CAP_SYS_NICE is required for microdroid_manager to adjust priority of the payload
+    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP SETUID SETGID SYS_NICE
     user root
     socket vm_payload_service stream 0666 system system
diff --git a/guest/microdroid_manager/src/main.rs b/guest/microdroid_manager/src/main.rs
index 4537834..a95bcb2 100644
--- a/guest/microdroid_manager/src/main.rs
+++ b/guest/microdroid_manager/src/main.rs
@@ -710,7 +710,21 @@
     info!("notifying payload started");
     service.notifyPayloadStarted()?;
 
-    let exit_status = command.spawn()?.wait()?;
+    let mut payload_process = command.spawn().context("failed to spawn payload process")?;
+    info!("payload pid = {:?}", payload_process.id());
+
+    // SAFETY: setpriority doesn't take any pointers
+    unsafe {
+        let ret = libc::setpriority(libc::PRIO_PROCESS, payload_process.id(), -20);
+        if ret != 0 {
+            error!(
+                "failed to adjust priority of the payload: {:#?}",
+                std::io::Error::last_os_error()
+            );
+        }
+    }
+
+    let exit_status = payload_process.wait()?;
     match exit_status.code() {
         Some(exit_code) => Ok(exit_code),
         None => Err(match exit_status.signal() {
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index 652ca90..c7f3dd6 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -147,6 +147,10 @@
 |  offset = (FOURTH - HEAD)     |
 |  size = (FOURTH_END - FOURTH) |
 +-------------------------------+
+|           [Entry 4]           | <-- Entry 4 is present since version 1.3
+|  offset = (FIFTH - HEAD)      |
+|  size = (FIFTH_END - FIFTH)   |
++-------------------------------+
 |              ...              |
 +-------------------------------+
 |           [Entry n]           |
@@ -168,7 +172,11 @@
 | {Fourth blob: VM reference DT}|
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FOURTH_END
 | (Padding to 8-byte alignment) |
-+===============================+
++===============================+ <-- FIFTH
+| {Fifth blob: Reserved Memory} |
++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ <-- FIFTH_END
+| (Padding to 8-byte alignment) |
++===============================+ <-- FIFTH
 |              ...              |
 +===============================+ <-- TAIL
 ```
@@ -238,6 +246,31 @@
 [secretkeeper_key]: https://android.googlesource.com/platform/system/secretkeeper/+/refs/heads/main/README.md#secretkeeper-public-key
 [vendor_hashtree_digest]: ../../build/microdroid/README.md#verification-of-vendor-image
 
+#### Version 1.3 {#pvmfw-data-v1-3}
+
+In version 1.3, a fifth blob is added.
+
+- entry 4, if present, contains potentially confidential data to be passed to
+  specific guests identified from their VM name. If the data is confidential,
+  this feature should only be used with guests using a fixed rollback
+  protection mechanism to prevent rollback attacks from a malicious host. Data
+  is passed as a reserved-memory region through the device tree with the
+  provided properties at an address which is implementation defined. Multiple
+  regions may be passed to the same guest. The format is as follows.
+
+  ```rust
+  #[repr(C)]
+  struct ReservedMemConfigEntry<const N: usize> {
+    /// The number of headers contained in this blob.
+    count: u32,
+    /// The [reserved memory headers](src/reserved_mem.rs) describing the passed data.
+    headers: [RMemHeader; N]
+    /// The actual data being passed. The reserved memory headers point to
+    /// offsets within this array.
+    data: [u8],
+  }
+  ```
+
 #### Virtual Platform DICE Chain Handover
 
 The format of the DICE chain entry mentioned above, compatible with the
diff --git a/guest/trusty/test_vm/AndroidTest.xml b/guest/trusty/test_vm/AndroidTest.xml
index 925b43c..43d9ef8 100644
--- a/guest/trusty/test_vm/AndroidTest.xml
+++ b/guest/trusty/test_vm/AndroidTest.xml
@@ -15,10 +15,10 @@
   limitations under the License.
   -->
     <configuration description="Runs {MODULE}">
-    <!-- object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController" -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController">
         <!--Skip the test when trusty VM is not enabled. -->
-        <!--option name="run-command" value="getprop trusty.test_vm.nonsecure_vm_ready | grep 1" /-->
-    <!--/object-->
+        <option name="run-command" value="getprop trusty.security_vm.enabled | grep 1" />
+    </object>
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <!-- Target Preparers - Run Shell Commands -->
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -38,13 +38,19 @@
         <option name="run-command" value="start storageproxyd_test_vm" />
         <option name="teardown-command" value="stop storageproxyd_test_vm" />
         <option name="teardown-command" value="killall storageproxyd_test_vm || true" />
+        <!--option name="teardown-command" value="rm -rf /data/local/trusty_test_vm"/-->
     </target_preparer>
     <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
         <option name="parse-gtest" value="true" />
         <option name="abort-if-device-lost" value="true"/>
         <option name="abort-if-root-lost" value="true" />
         <option name="per-binary-timeout" value="10m" />
+        <option name="test-command-line" key="com.android.trusty.rust.authmgr_be_lib.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.authmgr_be_lib.test"/>
         <option name="test-command-line" key="com.android.trusty.rust.hwcryptokey_test.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwcryptokey_test.test"/>
         <option name="test-command-line" key="com.android.trusty.rust.storage_unittest_aidl.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.storage_unittest_aidl.test"/>
     </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/tmp/trusty_test_vm/logs" />
+        <option name="clean-up" value="false"/>
+    </metrics_collector>
     </configuration>
diff --git a/guest/trusty/test_vm/README.md b/guest/trusty/test_vm/README.md
index 71368b5..81382c5 100644
--- a/guest/trusty/test_vm/README.md
+++ b/guest/trusty/test_vm/README.md
@@ -11,3 +11,16 @@
 The Trusty test_vm also includes the VINTF test which allows to check the vendor
 support of the Trusted HALs (version and API hash), against the expected
 compatibility matrix for a given Android Dessert Release.
+
+### instructions
+
+`atest -s <device-serial-port> VtsSeeHalTargetTest
+
+### test_vm console
+
+The test_vm console can be retrieved from `/data/local/tmp/trusty_test_vm/logs/console.log`.
+The script `trusty-vm-laucher.sh` uses `/apex/com.android.virt/bin/vm run` with the option
+`--console` to store the console log.
+
+This log can be consulted when the tests are running and will be uploaded
+by the Tradefed FilePullerLogCollector runner (see AndroidTest.xml).
diff --git a/guest/trusty/test_vm/TEST_MAPPING b/guest/trusty/test_vm/TEST_MAPPING
deleted file mode 100644
index aa9d65d..0000000
--- a/guest/trusty/test_vm/TEST_MAPPING
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "trusty_test_vm_presubmit": [
-  ],
-  "trusty_test_vm_postsubmit": [
-    {
-        "name": "TrustyTestVM_UnitTests"
-    }
-  ]
-}
diff --git a/guest/trusty/test_vm/trusty-vm-launcher.sh b/guest/trusty/test_vm/trusty-vm-launcher.sh
index cb8661f..079a66a 100755
--- a/guest/trusty/test_vm/trusty-vm-launcher.sh
+++ b/guest/trusty/test_vm/trusty-vm-launcher.sh
@@ -14,4 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-/apex/com.android.virt/bin/vm run /data/local/tmp/trusty_test_vm/trusty-test_vm-config.json
+mkdir -p /data/local/tmp/trusty_test_vm/logs || true
+/apex/com.android.virt/bin/vm run \
+   --console /data/local/tmp/trusty_test_vm/logs/console.log \
+   /data/local/tmp/trusty_test_vm/trusty-test_vm-config.json
diff --git a/guest/trusty/test_vm_os/AndroidTest.xml b/guest/trusty/test_vm_os/AndroidTest.xml
index be5c467..5adafff 100644
--- a/guest/trusty/test_vm_os/AndroidTest.xml
+++ b/guest/trusty/test_vm_os/AndroidTest.xml
@@ -15,10 +15,10 @@
   limitations under the License.
   -->
     <configuration description="Runs {MODULE}">
-    <!-- object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController" -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController">
         <!--Skip the test when trusty VM is not enabled. -->
-        <!--option name="run-command" value="getprop trusty.test_vm.nonsecure_vm_ready | grep 1" /-->
-    <!--/object-->
+        <option name="run-command" value="getprop trusty.security_vm.enabled | grep 1" />
+    </object>
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <!-- Target Preparers - Run Shell Commands -->
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -38,6 +38,7 @@
         <option name="run-command" value="start storageproxyd_test_vm_os" />
         <option name="teardown-command" value="stop storageproxyd_test_vm_os" />
         <option name="teardown-command" value="killall storageproxyd_test_vm_os || true" />
+        <!--option name="teardown-command" value="rm -rf /data/local/trusty_test_vm_os"/-->
     </target_preparer>
     <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
         <option name="parse-gtest" value="true" />
@@ -79,4 +80,10 @@
         <option name="test-command-line" key="com.android.trusty.rust.binder_rpc_test.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.binder_rpc_test.test"/>
         <option name="test-command-line" key="com.android.trusty.binder.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.binder.test"/>
     </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/tmp/trusty_test_vm_os/logs" />
+        <option name="collect-on-run-ended-only" value="true" />
+        <option name="clean-up" value="true"/>
+        <option name="collect-on-run-ended-only" value="false" />
+    </metrics_collector>
     </configuration>
diff --git a/guest/trusty/test_vm_os/README.md b/guest/trusty/test_vm_os/README.md
index 4d65d9f..b37a4da 100644
--- a/guest/trusty/test_vm_os/README.md
+++ b/guest/trusty/test_vm_os/README.md
@@ -5,3 +5,6 @@
 - Trusty kernel OS test
 - Trusty/Binder IPC tests
 - Trusty user-space tests for service TAs (DT tree for example)
+
+
+see instructions at [test_vm/README.md](../test_vm/README.md)
diff --git a/guest/trusty/test_vm_os/TEST_MAPPING b/guest/trusty/test_vm_os/TEST_MAPPING
deleted file mode 100644
index 1506720..0000000
--- a/guest/trusty/test_vm_os/TEST_MAPPING
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "trusty_test_vm_presubmit": [
-  ],
-  "trusty_test_vm_postsubmit": [
-    {
-        "name": "TrustyVMOS_UnitTests"
-    }
-  ]
-}
diff --git a/guest/trusty/test_vm_os/trusty-vm-launcher.sh b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
index 497b188..bc256ed 100755
--- a/guest/trusty/test_vm_os/trusty-vm-launcher.sh
+++ b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
@@ -14,4 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-/apex/com.android.virt/bin/vm run /data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json
+mkdir -p /data/local/tmp/trusty_test_vm_os/logs || true
+/apex/com.android.virt/bin/vm run \
+   --console /data/local/tmp/trusty_test_vm_os/logs/console.log \
+   /data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json
diff --git a/libs/dice/open_dice/src/error.rs b/libs/dice/open_dice/src/error.rs
index c9eb5cc..87d463e 100644
--- a/libs/dice/open_dice/src/error.rs
+++ b/libs/dice/open_dice/src/error.rs
@@ -33,6 +33,8 @@
     UnsupportedKeyAlgorithm(coset::iana::Algorithm),
     /// A failed fallible allocation. Used in no_std environments.
     MemoryAllocationError,
+    /// DICE chain not found in artifacts.
+    DiceChainNotFound,
 }
 
 /// This makes `DiceError` accepted by anyhow.
@@ -51,6 +53,7 @@
                 write!(f, "Unsupported key algorithm: {algorithm:?}")
             }
             Self::MemoryAllocationError => write!(f, "Memory allocation failed"),
+            Self::DiceChainNotFound => write!(f, "DICE chain not found in artifacts"),
         }
     }
 }
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index d793218..2b7b740 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -17,7 +17,7 @@
 //! of this buffer may fail and callers will see Error::MemoryAllocationError.
 //! When running with std, allocation may fail.
 
-use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow, DiceConfigValues};
+use crate::bcc::{bcc_format_config_descriptor, bcc_main_flow, BccHandover, DiceConfigValues};
 use crate::dice::{
     dice_main_flow, Cdi, CdiValues, DiceArtifacts, InputValues, CDI_SIZE, PRIVATE_KEY_SEED_SIZE,
     PRIVATE_KEY_SIZE,
@@ -60,6 +60,20 @@
     }
 }
 
+impl TryFrom<BccHandover<'_>> for OwnedDiceArtifacts {
+    type Error = DiceError;
+
+    fn try_from(artifacts: BccHandover<'_>) -> Result<Self> {
+        let cdi_attest = artifacts.cdi_attest().to_vec().try_into().unwrap();
+        let cdi_seal = artifacts.cdi_seal().to_vec().try_into().unwrap();
+        let bcc = artifacts
+            .bcc()
+            .map(|bcc_slice| bcc_slice.to_vec())
+            .ok_or(DiceError::DiceChainNotFound)?;
+        Ok(OwnedDiceArtifacts { cdi_values: CdiValues { cdi_attest, cdi_seal }, bcc })
+    }
+}
+
 /// Retries the given function with bigger measured buffer size.
 fn retry_with_measured_buffer<F>(mut f: F) -> Result<Vec<u8>>
 where